From e2a0db74cfa4ed73692ec5d0af944660bb4b688c Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Tue, 6 Feb 2018 17:52:07 -0800 Subject: [PATCH 001/906] Python3 support of docs generation --- tensorflow/docs_src/community/documentation.md | 18 +++--------------- tensorflow/tools/docs/BUILD | 2 +- tensorflow/tools/docs/build_docs_test.py | 4 ---- tensorflow/tools/docs/generate_lib.py | 2 -- tensorflow/tools/docs/generate_lib_test.py | 3 --- tensorflow/tools/docs/parser.py | 4 ++-- tensorflow/tools/docs/parser_test.py | 4 ---- tensorflow/tools/docs/pretty_docs.py | 12 ++++++------ tensorflow/workspace.bzl | 11 ----------- 9 files changed, 12 insertions(+), 48 deletions(-) diff --git a/tensorflow/docs_src/community/documentation.md b/tensorflow/docs_src/community/documentation.md index 003e0a25ec..8d55148e48 100644 --- a/tensorflow/docs_src/community/documentation.md +++ b/tensorflow/docs_src/community/documentation.md @@ -148,19 +148,7 @@ viewing. Do not include url parameters in the source code URL. Before building the documentation, you must first set up your environment by doing the following: -1. If pip isn't installed on your machine, install it now by issuing the -following command: - - $ sudo easy_install pip - -2. Use pip to install codegen, mock, and pandas by issuing the following - command (Note: If you are using - a [virtualenv](https://virtualenv.pypa.io/en/stable/) to manage your - dependencies, you may not want to use sudo for these installations): - - $ sudo pip install codegen mock pandas - -3. If bazel is not installed on your machine, install it now. If you are on +1. If bazel is not installed on your machine, install it now. If you are on Linux, install bazel by issuing the following command: $ sudo apt-get install bazel # Linux @@ -168,10 +156,10 @@ following command: If you are on Mac OS, find bazel installation instructions on [this page](https://bazel.build/versions/master/docs/install.html#mac-os-x). -4. Change directory to the top-level `tensorflow` directory of the TensorFlow +2. Change directory to the top-level `tensorflow` directory of the TensorFlow source code. -5. Run the `configure` script and answer its prompts appropriately for your +3. Run the `configure` script and answer its prompts appropriately for your system. $ ./configure diff --git a/tensorflow/tools/docs/BUILD b/tensorflow/tools/docs/BUILD index 8f10bc9e0c..cafa1f7eb3 100644 --- a/tensorflow/tools/docs/BUILD +++ b/tensorflow/tools/docs/BUILD @@ -37,7 +37,7 @@ py_library( srcs = ["parser.py"], srcs_version = "PY2AND3", visibility = ["//visibility:public"], - deps = ["@com_github_andreif_codegen"], + deps = ["@astor_archive//:astor"], ) py_test( diff --git a/tensorflow/tools/docs/build_docs_test.py b/tensorflow/tools/docs/build_docs_test.py index ae293f6576..2e8f634e7c 100644 --- a/tensorflow/tools/docs/build_docs_test.py +++ b/tensorflow/tools/docs/build_docs_test.py @@ -39,10 +39,6 @@ class Flags(object): class BuildDocsTest(googletest.TestCase): def testBuildDocs(self): - if sys.version_info >= (3, 0): - print('Warning: Doc generation is not supported from python3.') - return - doc_generator = generate_lib.DocGenerator() doc_generator.set_py_modules([('tf', tf), ('tfdbg', tf_debug)]) diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index 003f972070..635408d87f 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -455,8 +455,6 @@ class DocGenerator(object): """Main entry point for generating docs.""" def __init__(self): - if sys.version_info >= (3, 0): - sys.exit('Doc generation is not supported from python3.') self.argument_parser = argparse.ArgumentParser() self._py_modules = None self._private_map = _get_default_private_map() diff --git a/tensorflow/tools/docs/generate_lib_test.py b/tensorflow/tools/docs/generate_lib_test.py index 1ceaf31f1c..ea6d28a02b 100644 --- a/tensorflow/tools/docs/generate_lib_test.py +++ b/tensorflow/tools/docs/generate_lib_test.py @@ -52,9 +52,6 @@ class DummyVisitor(object): class GenerateTest(googletest.TestCase): def test_write(self): - if sys.version_info >= (3, 0): - self.skipTest('Warning: Doc generation is not supported from python3.') - module = sys.modules[__name__] index = { diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index 3db164c2b5..1798378d55 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -26,7 +26,7 @@ import os import re import sys -import codegen +import astor import six from google.protobuf.message import Message as ProtoMessage @@ -705,7 +705,7 @@ def _generate_signature(func, reverse_index): if id(default) in reverse_index: default_text = reverse_index[id(default)] elif ast_default is not None: - default_text = codegen.to_source(ast_default) + default_text = astor.to_source(ast_default) if default_text != repr(default): # This may be an internal name. If so, handle the ones we know about. # TODO(wicke): This should be replaced with a lookup in the index. diff --git a/tensorflow/tools/docs/parser_test.py b/tensorflow/tools/docs/parser_test.py index 8a0e9af521..7d2bf9177a 100644 --- a/tensorflow/tools/docs/parser_test.py +++ b/tensorflow/tools/docs/parser_test.py @@ -523,10 +523,6 @@ class TestParseFunctionDetails(googletest.TestCase): class TestGenerateSignature(googletest.TestCase): def test_known_object(self): - if sys.version_info >= (3, 0): - print('Warning: Doc generation is not supported from python3.') - return - known_object = object() reverse_index = {id(known_object): 'location.of.object.in.api'} diff --git a/tensorflow/tools/docs/pretty_docs.py b/tensorflow/tools/docs/pretty_docs.py index 543b5fa6fe..55ab5bdd49 100644 --- a/tensorflow/tools/docs/pretty_docs.py +++ b/tensorflow/tools/docs/pretty_docs.py @@ -101,7 +101,7 @@ def _build_class_page(page_info): link_template = '[`{short_name}`]({url})' parts.append(', '.join( - link_template.format(**base.__dict__) for base in page_info.bases)) + link_template.format(**base._asdict()) for base in page_info.bases)) parts.append('\n\n') @@ -159,7 +159,7 @@ def _build_class_page(page_info): h3 = ('

' '{short_name}' '

\n\n') - parts.append(h3.format(**method_info.__dict__)) + parts.append(h3.format(**method_info._asdict())) if method_info.signature is not None: parts.append(_build_signature(method_info, use_full_name=False)) @@ -217,7 +217,7 @@ def _build_module_page(page_info): template = '[`{short_name}`]({url}) module' for item in page_info.modules: - parts.append(template.format(**item.__dict__)) + parts.append(template.format(**item._asdict())) if item.doc.brief: parts.append(': ' + item.doc.brief) @@ -229,7 +229,7 @@ def _build_module_page(page_info): template = '[`class {short_name}`]({url})' for item in page_info.classes: - parts.append(template.format(**item.__dict__)) + parts.append(template.format(**item._asdict())) if item.doc.brief: parts.append(': ' + item.doc.brief) @@ -241,7 +241,7 @@ def _build_module_page(page_info): template = '[`{short_name}(...)`]({url})' for item in page_info.functions: - parts.append(template.format(**item.__dict__)) + parts.append(template.format(**item._asdict())) if item.doc.brief: parts.append(': ' + item.doc.brief) @@ -254,7 +254,7 @@ def _build_module_page(page_info): parts.append('## Other Members\n\n') for item in page_info.other_members: - parts.append('`{short_name}`\n\n'.format(**item.__dict__)) + parts.append('`{short_name}`\n\n'.format(**item._asdict())) return ''.join(parts) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index eca744a920..4a2274eb1a 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -328,17 +328,6 @@ def tf_workspace(path_prefix="", tf_repo_name=""): build_file = str(Label("//third_party:backports_weakref.BUILD")), ) - tf_http_archive( - name = "com_github_andreif_codegen", - urls = [ - "https://mirror.bazel.build/github.com/andreif/codegen/archive/1.0.tar.gz", - "https://github.com/andreif/codegen/archive/1.0.tar.gz", - ], - sha256 = "2dadd04a2802de27e0fe5a19b76538f6da9d39ff244036afa00c1bba754de5ee", - strip_prefix = "codegen-1.0", - build_file = str(Label("//third_party:codegen.BUILD")), - ) - filegroup_external( name = "org_python_license", licenses = ["notice"], # Python 2.0 -- GitLab From 4f5d9a88f84e2261808bc986ece951e6e1d10725 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Tue, 6 Feb 2018 17:55:15 -0800 Subject: [PATCH 002/906] remove unused codegen.BUILD --- third_party/codegen.BUILD | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 third_party/codegen.BUILD diff --git a/third_party/codegen.BUILD b/third_party/codegen.BUILD deleted file mode 100644 index df436c8163..0000000000 --- a/third_party/codegen.BUILD +++ /dev/null @@ -1,16 +0,0 @@ -# -*- mode: python; -*- -# -# Description: -# Extension to ast that allow ast -> python code generation. - -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) # New BSD - -exports_files(["LICENSE"]) - -py_library( - name = "com_github_andreif_codegen", - srcs = glob(["codegen.py"]), - srcs_version = "PY2AND3", -) -- GitLab From 15f3b920ad7eb7fcca3afee14d16049db2046d4b Mon Sep 17 00:00:00 2001 From: Nathan Luehr Date: Wed, 14 Feb 2018 16:27:23 -0800 Subject: [PATCH 003/906] Fix __shared__ types with non-empty constructor std::complex has a non-empty constructor (zero assignment) that is not compatible with CUDA __shared__ memory. This fixes current reliance on undefined behavior. (and removes an unnecessary run-time initialization). --- .../core/kernels/reduction_gpu_kernels.cu.h | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h index 15ae4c1fc5..95a3e222b5 100644 --- a/tensorflow/core/kernels/reduction_gpu_kernels.cu.h +++ b/tensorflow/core/kernels/reduction_gpu_kernels.cu.h @@ -244,6 +244,33 @@ __global__ void RowReduceKernel( if (row < num_rows && lane == 0) out[row] = sum; } +template +struct storage_type { + T1 val; + __host__ __device__ storage_type() {} + __host__ __device__ operator T1() { return val; } + __host__ __device__ storage_type& operator=(const T1& in) { + val = in; + return *this; + } +}; + +template +struct storage_type> { + T2 real; + T2 imag; + __host__ __device__ storage_type() {} + __host__ __device__ operator std::complex() { + return std::complex(real, imag); + } + __host__ __device__ storage_type>& operator=( + const std::complex& in) { + real = in.real(); + imag = in.imag(); + return *this; + } +}; + // Works only if there are <= 16 columns // each warps sums over multiple rows at once template @@ -268,7 +295,7 @@ __global__ void ColumnReduceMax16ColumnsKernel( // 1D array necessary due to bug in CUDA 9 compiler. // TODO(nluehr) revert to 2D array when compiler is ready. - __shared__ value_type partial_sums[32 * 33]; + __shared__ storage_type partial_sums[32 * 33]; row += rows_per_warp * gridDim.y * blockDim.y; for (; row < num_rows; row += rows_per_warp * gridDim.y * blockDim.y) { @@ -294,7 +321,8 @@ __global__ void ColumnReduceMax16ColumnsKernel( if (blockDim.y > 1) { for (int row = 1; row < blockDim.y; ++row) { - s = op(s, partial_sums[threadIdx.x * 33 + row]); + value_type t = partial_sums[threadIdx.x * 33 + row]; + s = op(s, t); } } @@ -316,7 +344,7 @@ __global__ void ColumnReduceKernel( // 1D array necessary due to bug in CUDA 9 compiler. // TODO(nluehr) revert to 2D array when compiler is ready. - __shared__ value_type partial_sums[32 * 33]; + __shared__ storage_type partial_sums[32 * 33]; row += gridDim.y * blockDim.y; @@ -347,7 +375,8 @@ __global__ void ColumnReduceKernel( min(blockDim.y, num_rows - blockIdx.y * blockDim.y); for (int row = 1; row < numRowsThisBlock; ++row) { - s = op(s, partial_sums[threadIdx.x * 33 + row]); + value_type t = partial_sums[threadIdx.x * 33 + row]; + s = op(s, t); } out[col * gridDim.y + blockIdx.y] = s; -- GitLab From 08a3509b2ecbd9fdfdb4f50b81e11f491291647e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Sun, 18 Feb 2018 17:02:13 +0100 Subject: [PATCH 004/906] Add NumPy style warning when casting complex to float --- tensorflow/python/ops/math_ops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index da9957aa2a..2c422ebca4 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -169,6 +169,7 @@ from tensorflow.python.ops import gen_sparse_ops from tensorflow.python.ops import gen_spectral_ops from tensorflow.python.ops import gen_state_ops from tensorflow.python.ops import state_ops +from tensorflow.python.platform import tf_logging as logging # go/tf-wildcard-import # pylint: disable=wildcard-import from tensorflow.python.ops.gen_math_ops import * @@ -830,6 +831,8 @@ def to_float(x, name="ToFloat"): Raises: TypeError: If `x` cannot be cast to the `float32`. """ + if x.dtype.is_complex: + logging.warn('Casting complex to real discards imaginary part.') return cast(x, dtypes.float32, name=name) -- GitLab From b808636c795e7a96a1e7264076a95d3e9343f430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Sun, 18 Feb 2018 20:46:07 +0100 Subject: [PATCH 005/906] Fix quotes --- tensorflow/python/ops/math_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index 2c422ebca4..4c7dc9559f 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -832,7 +832,7 @@ def to_float(x, name="ToFloat"): TypeError: If `x` cannot be cast to the `float32`. """ if x.dtype.is_complex: - logging.warn('Casting complex to real discards imaginary part.') + logging.warn("Casting complex to real discards imaginary part.") return cast(x, dtypes.float32, name=name) -- GitLab From 62a05fe71ba5157e7abeb291f4b8b6ac7abf97fb Mon Sep 17 00:00:00 2001 From: DavidNorman Date: Tue, 27 Feb 2018 11:51:05 +0000 Subject: [PATCH 006/906] Ensure that the backend_deps is a non-frozen object --- tensorflow/compiler/xla/tests/build_defs.bzl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/tests/build_defs.bzl b/tensorflow/compiler/xla/tests/build_defs.bzl index 610302ac12..eac2eb286c 100644 --- a/tensorflow/compiler/xla/tests/build_defs.bzl +++ b/tensorflow/compiler/xla/tests/build_defs.bzl @@ -137,7 +137,8 @@ def xla_test(name, backend_deps += ["//tensorflow/compiler/xla/tests:test_macros_gpu"] this_backend_tags += ["requires-gpu-sm35"] elif backend in plugins: - backend_deps = plugins[backend]["deps"] + backend_deps = [] + backend_deps += plugins[backend]["deps"] this_backend_copts += plugins[backend]["copts"] this_backend_tags += plugins[backend]["tags"] this_backend_args += plugins[backend]["args"] -- GitLab From 2e98952221bfe83fadc3054e66b2ff3c23c44a24 Mon Sep 17 00:00:00 2001 From: DavidNorman Date: Tue, 27 Feb 2018 13:52:13 +0000 Subject: [PATCH 007/906] Allow the large R1 slice tests to be disabled --- tensorflow/compiler/xla/tests/slice_test.cc | 35 +++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/tests/slice_test.cc b/tensorflow/compiler/xla/tests/slice_test.cc index fe36df160d..50cd56d2d4 100644 --- a/tensorflow/compiler/xla/tests/slice_test.cc +++ b/tensorflow/compiler/xla/tests/slice_test.cc @@ -211,6 +211,9 @@ class SliceR1Test : public ClientLibraryTestBase, } }; +// A version of SliceR1Test used to label and disable 'large' tests +class SliceR1LargeTest : public SliceR1Test {}; + string SliceR1TestDataToString(const ::testing::TestParamInfo& data) { const R1Spec& spec = data.param; return ::tensorflow::strings::Printf("%lld_%lld_%lld_%lld", spec.input_dim0, @@ -230,6 +233,18 @@ XLA_TEST_P(SliceR1Test, DoIt_U64) { Run(GetParam()); } XLA_TEST_P(SliceR1Test, DoIt_S64) { Run(GetParam()); } +XLA_TEST_P(SliceR1LargeTest, DoIt_F32) { Run(GetParam()); } + +XLA_TEST_P(SliceR1LargeTest, DoIt_F64) { Run(GetParam()); } + +XLA_TEST_P(SliceR1LargeTest, DoIt_U32) { Run(GetParam()); } + +XLA_TEST_P(SliceR1LargeTest, DoIt_S32) { Run(GetParam()); } + +XLA_TEST_P(SliceR1LargeTest, DoIt_U64) { Run(GetParam()); } + +XLA_TEST_P(SliceR1LargeTest, DoIt_S64) { Run(GetParam()); } + // Tests for R1 slice ops. // The format for each testcase is {input size, start, limit, stride}. // clang-format off @@ -237,12 +252,6 @@ INSTANTIATE_TEST_CASE_P( SliceR1TestInstantiation, SliceR1Test, ::testing::Values( -// TODO(b/69425338): This uses too much memory on GPU. -#ifndef XLA_TEST_BACKEND_GPU - R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024, 12 * 1024 * 1024, 1}, - R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024 + 1, 12 * 1024 * 1024 - 1, 1}, - R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024 - 1, 12 * 1024 * 1024 + 1, 1}, -#endif R1Spec{10, 0, 0, 1}, R1Spec{10, 7, 7, 1}, R1Spec{10, 0, 5, 1}, @@ -278,6 +287,20 @@ INSTANTIATE_TEST_CASE_P( SliceR1TestDataToString ); +// TODO(b/69425338): This uses too much memory on GPU. +#ifndef XLA_TEST_BACKEND_GPU +INSTANTIATE_TEST_CASE_P( + SliceR1TestBigSlicesInstantiation, + SliceR1LargeTest, + ::testing::Values( + R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024, 12 * 1024 * 1024, 1}, + R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024 + 1, 12 * 1024 * 1024 - 1, 1}, + R1Spec{16 * 1024 * 1024, 4 * 1024 * 1024 - 1, 12 * 1024 * 1024 + 1, 1} + ), + SliceR1TestDataToString +); +#endif + INSTANTIATE_TEST_CASE_P( SliceStridedR1TestInstantiation, SliceR1Test, -- GitLab From 70b60d9cce9a7879fbff396f283f19bed3b39793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Mon, 5 Mar 2018 14:08:19 +0100 Subject: [PATCH 008/906] Move complex->float warning into tf.cast --- tensorflow/python/ops/math_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index 56d58016b8..1608393c16 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -762,6 +762,8 @@ def cast(x, dtype, name=None): Raises: TypeError: If `x` cannot be cast to the `dtype`. """ + if x.dtype.is_complex and dtype.is_floating: + logging.warn("Casting complex to real discards imaginary part.") base_type = dtypes.as_dtype(dtype).base_dtype with ops.name_scope(name, "Cast", [x]) as name: if isinstance(x, sparse_tensor.SparseTensor): @@ -826,8 +828,6 @@ def to_float(x, name="ToFloat"): Raises: TypeError: If `x` cannot be cast to the `float32`. """ - if x.dtype.is_complex: - logging.warn("Casting complex to real discards imaginary part.") return cast(x, dtypes.float32, name=name) -- GitLab From cf897725ab8c3f09d973c5f242b05ca7eb258801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Mon, 5 Mar 2018 18:07:22 +0100 Subject: [PATCH 009/906] Check dtype after convert_to_tensor --- tensorflow/python/ops/math_ops.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index 1608393c16..e315a09ea9 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -762,22 +762,22 @@ def cast(x, dtype, name=None): Raises: TypeError: If `x` cannot be cast to the `dtype`. """ - if x.dtype.is_complex and dtype.is_floating: - logging.warn("Casting complex to real discards imaginary part.") base_type = dtypes.as_dtype(dtype).base_dtype with ops.name_scope(name, "Cast", [x]) as name: if isinstance(x, sparse_tensor.SparseTensor): values_cast = cast(x.values, base_type, name=name) - return sparse_tensor.SparseTensor(x.indices, values_cast, x.dense_shape) + x = sparse_tensor.SparseTensor(x.indices, values_cast, x.dense_shape) else: # TODO(josh11b): If x is not already a Tensor, we could return # ops.convert_to_tensor(x, dtype=dtype, ...) here, but that # allows some conversions that cast() can't do, e.g. casting numbers to # strings. x = ops.convert_to_tensor(x, name="x") - if x.dtype.base_dtype == base_type: - return x - return gen_math_ops.cast(x, base_type, name=name) + if x.dtype.base_dtype != base_type: + x = gen_math_ops.cast(x, base_type, name=name) + if x.dtype.is_complex and dtype.is_floating: + logging.warn("Casting complex to real discards imaginary part.") + return x @tf_export("saturate_cast") -- GitLab From 955f41c5f2240495a086b503e54eac6928876aca Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Wed, 7 Mar 2018 14:04:26 -0800 Subject: [PATCH 010/906] Cleanup `astor` output to match `codegen` output. The default `astor` output messes up the function signature docs for many docs without a bit of cleanup. With this change the only differences I see are parens around lambdas and math expressions in default arguments. --- tensorflow/tools/docs/parser.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index 1798378d55..0fcd0abc4a 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -650,6 +650,9 @@ def _remove_first_line_indent(string): return '\n'.join([line[indent:] for line in string.split('\n')]) +PAREN_NUMBER_RE = re.compile("^\(([0-9.e-]+)\)") + + def _generate_signature(func, reverse_index): """Given a function, returns a list of strings representing its args. @@ -705,7 +708,11 @@ def _generate_signature(func, reverse_index): if id(default) in reverse_index: default_text = reverse_index[id(default)] elif ast_default is not None: - default_text = astor.to_source(ast_default) + default_text = ( + astor.to_source(ast_default).rstrip('\n').replace('\t','\\t') + .replace('\n','\\n').replace('"""',"'")) + default_text = PAREN_NUMBER_RE.sub('\\1',default_text) + if default_text != repr(default): # This may be an internal name. If so, handle the ones we know about. # TODO(wicke): This should be replaced with a lookup in the index. -- GitLab From c22d11f4fcc2801d0a5de98a84461e03e1bcb674 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Wed, 7 Mar 2018 14:14:08 -0800 Subject: [PATCH 011/906] add back docs --- tensorflow/docs_src/community/documentation.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tensorflow/docs_src/community/documentation.md b/tensorflow/docs_src/community/documentation.md index 8d55148e48..f7b7ba14e5 100644 --- a/tensorflow/docs_src/community/documentation.md +++ b/tensorflow/docs_src/community/documentation.md @@ -148,7 +148,19 @@ viewing. Do not include url parameters in the source code URL. Before building the documentation, you must first set up your environment by doing the following: -1. If bazel is not installed on your machine, install it now. If you are on +1. If pip isn't installed on your machine, install it now by issuing the +following command: + + $ sudo easy_install pip + +2. Use pip to install mock and pandas by issuing the following + command (Note: If you are using + a [virtualenv](https://virtualenv.pypa.io/en/stable/) to manage your + dependencies, you may not want to use sudo for these installations): + + $ sudo pip install mock pandas + +3. If bazel is not installed on your machine, install it now. If you are on Linux, install bazel by issuing the following command: $ sudo apt-get install bazel # Linux @@ -156,10 +168,10 @@ doing the following: If you are on Mac OS, find bazel installation instructions on [this page](https://bazel.build/versions/master/docs/install.html#mac-os-x). -2. Change directory to the top-level `tensorflow` directory of the TensorFlow +4. Change directory to the top-level `tensorflow` directory of the TensorFlow source code. -3. Run the `configure` script and answer its prompts appropriately for your +5. Run the `configure` script and answer its prompts appropriately for your system. $ ./configure -- GitLab From cbb517551964879dcb6eac2b00bf74db6c827975 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Wed, 7 Mar 2018 14:54:24 -0800 Subject: [PATCH 012/906] Revert "add back docs" This reverts commit c22d11f4fcc2801d0a5de98a84461e03e1bcb674. --- tensorflow/docs_src/community/documentation.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/tensorflow/docs_src/community/documentation.md b/tensorflow/docs_src/community/documentation.md index f7b7ba14e5..8d55148e48 100644 --- a/tensorflow/docs_src/community/documentation.md +++ b/tensorflow/docs_src/community/documentation.md @@ -148,19 +148,7 @@ viewing. Do not include url parameters in the source code URL. Before building the documentation, you must first set up your environment by doing the following: -1. If pip isn't installed on your machine, install it now by issuing the -following command: - - $ sudo easy_install pip - -2. Use pip to install mock and pandas by issuing the following - command (Note: If you are using - a [virtualenv](https://virtualenv.pypa.io/en/stable/) to manage your - dependencies, you may not want to use sudo for these installations): - - $ sudo pip install mock pandas - -3. If bazel is not installed on your machine, install it now. If you are on +1. If bazel is not installed on your machine, install it now. If you are on Linux, install bazel by issuing the following command: $ sudo apt-get install bazel # Linux @@ -168,10 +156,10 @@ following command: If you are on Mac OS, find bazel installation instructions on [this page](https://bazel.build/versions/master/docs/install.html#mac-os-x). -4. Change directory to the top-level `tensorflow` directory of the TensorFlow +2. Change directory to the top-level `tensorflow` directory of the TensorFlow source code. -5. Run the `configure` script and answer its prompts appropriately for your +3. Run the `configure` script and answer its prompts appropriately for your system. $ ./configure -- GitLab From d34eaf348848fe153a5fd245aa75c2ca32973b36 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Wed, 7 Mar 2018 21:53:25 -0800 Subject: [PATCH 013/906] fix encoding and lint --- tensorflow/tools/docs/build_docs_test.py | 1 - tensorflow/tools/docs/generate_lib.py | 13 ++++++------- tensorflow/tools/docs/parser.py | 6 +++--- tensorflow/tools/docs/py_guide_parser.py | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tensorflow/tools/docs/build_docs_test.py b/tensorflow/tools/docs/build_docs_test.py index 2e8f634e7c..0cbf8b478f 100644 --- a/tensorflow/tools/docs/build_docs_test.py +++ b/tensorflow/tools/docs/build_docs_test.py @@ -19,7 +19,6 @@ from __future__ import division from __future__ import print_function import os -import sys import textwrap import tensorflow as tf diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index 635408d87f..a7ab0fa538 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -21,7 +21,6 @@ from __future__ import print_function import argparse import fnmatch import os -import sys import six @@ -134,8 +133,8 @@ def write_docs(output_dir, parser_config, yaml_toc, root_title='TensorFlow'): try: if not os.path.exists(directory): os.makedirs(directory) - with open(path, 'w') as f: - f.write(pretty_docs.build_md_page(page_info)) + with open(path, 'wb') as f: + f.write(pretty_docs.build_md_page(page_info).encode('utf-8')) except OSError as e: print('Cannot write documentation for %s to %s: %s' % (full_name, directory, e)) @@ -434,19 +433,19 @@ def _other_docs(src_dir, output_dir, reference_resolver, file_pattern='*.md'): full_out_path = os.path.join(output_dir, suffix) if not fnmatch.fnmatch(base_name, file_pattern): print('Copying un-matched file %s...' % suffix) - open(full_out_path, 'w').write(open(full_in_path).read()) + open(full_out_path, 'wb').write(open(full_in_path, 'rb').read()) continue if dirpath.endswith('/api_guides/python'): print('Processing Python guide %s...' % base_name) content = tag_updater.process(full_in_path) else: print('Processing doc %s...' % suffix) - content = open(full_in_path).read() + content = open(full_in_path, 'rb').read().decode('utf-8') content = reference_resolver.replace_references(content, relative_path_to_root) - with open(full_out_path, 'w') as f: - f.write(content) + with open(full_out_path, 'wb') as f: + f.write(content.encode('utf-8')) print('Done.') diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index 0fcd0abc4a..dd0351b4c6 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -709,9 +709,9 @@ def _generate_signature(func, reverse_index): default_text = reverse_index[id(default)] elif ast_default is not None: default_text = ( - astor.to_source(ast_default).rstrip('\n').replace('\t','\\t') - .replace('\n','\\n').replace('"""',"'")) - default_text = PAREN_NUMBER_RE.sub('\\1',default_text) + astor.to_source(ast_default).rstrip('\n').replace('\t', '\\t') + .replace('\n', '\\n').replace('"""', "'")) + default_text = PAREN_NUMBER_RE.sub('\\1', default_text) if default_text != repr(default): # This may be an internal name. If so, handle the ones we know about. diff --git a/tensorflow/tools/docs/py_guide_parser.py b/tensorflow/tools/docs/py_guide_parser.py index 216353ecee..328f42d18f 100644 --- a/tensorflow/tools/docs/py_guide_parser.py +++ b/tensorflow/tools/docs/py_guide_parser.py @@ -44,7 +44,7 @@ class PyGuideParser(object): def process(self, full_path): """Read and process the file at `full_path`.""" - md_string = open(full_path).read() + md_string = open(full_path, 'rb').read().decode('utf-8') self._lines = md_string.split('\n') seen = set() -- GitLab From f7a04228e0368f3c9bad22a66fe7267e41ecb128 Mon Sep 17 00:00:00 2001 From: DavidNorman Date: Thu, 8 Mar 2018 07:05:53 +0000 Subject: [PATCH 014/906] Register half in some ops which support all floating point types --- tensorflow/core/ops/nn_ops.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 910fbaca9e..6d4a3fda51 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -472,7 +472,7 @@ REGISTER_OP("DepthwiseConv2dNativeBackpropInput") .Input("filter: T") .Input("out_backprop: T") .Output("output: T") - .Attr("T: {bfloat16, float, double}") + .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) @@ -490,7 +490,7 @@ REGISTER_OP("DepthwiseConv2dNativeBackpropFilter") .Input("filter_sizes: int32") .Input("out_backprop: T") .Output("output: T") - .Attr("T: {bfloat16, float, double}") + .Attr("T: {half, bfloat16, float, double}") .Attr("strides: list(int)") .Attr(GetPaddingAttrString()) .Attr(GetConvnetDataFormatAttrString()) @@ -589,7 +589,7 @@ REGISTER_OP("AvgPool3D") .Attr("strides: list(int) >= 5") .Attr(GetPaddingAttrString()) .Attr(GetConvnet3dDataFormatAttrString()) - .Attr("T: {bfloat16, float, double}") + .Attr("T: {half, bfloat16, float, double}") .SetShapeFn(shape_inference::Pool3DShape); REGISTER_OP("AvgPool3DGrad") @@ -600,7 +600,7 @@ REGISTER_OP("AvgPool3DGrad") .Attr("strides: list(int) >= 5") .Attr(GetPaddingAttrString()) .Attr(GetConvnet3dDataFormatAttrString()) - .Attr("T: {bfloat16, float, double}") + .Attr("T: {half, bfloat16, float, double}") .SetShapeFn([](InferenceContext* c) { ShapeHandle s; TF_RETURN_IF_ERROR(c->MakeShapeFromShapeTensor(0, &s)); @@ -618,7 +618,7 @@ REGISTER_OP("MaxPool3D") .Attr("strides: list(int) >= 5") .Attr(GetPaddingAttrString()) .Attr(GetConvnet3dDataFormatAttrString()) - .Attr("T: {bfloat16, float}") + .Attr("T: {half, bfloat16, float}") .SetShapeFn(shape_inference::Pool3DShape); REGISTER_OP("MaxPool3DGrad") @@ -630,8 +630,8 @@ REGISTER_OP("MaxPool3DGrad") .Attr("strides: list(int) >= 5") .Attr(GetPaddingAttrString()) .Attr(GetConvnet3dDataFormatAttrString()) - .Attr("T: {bfloat16, float} = DT_FLOAT") - .Attr("TInput: {bfloat16, float} = DT_FLOAT") + .Attr("T: {half, bfloat16, float} = DT_FLOAT") + .Attr("TInput: {half, bfloat16, float} = DT_FLOAT") .SetShapeFn([](InferenceContext* c) { return UnchangedShapeWithRank(c, 5); }); -- GitLab From 3bed12b81fe5ffc04e14ccaaf1b25ace4222f505 Mon Sep 17 00:00:00 2001 From: Yifei Feng Date: Thu, 8 Mar 2018 13:56:11 -0800 Subject: [PATCH 015/906] Update version string to 1.7.0rc0 everywhere. --- tensorflow/core/public/version.h | 4 ++-- tensorflow/docs_src/install/install_c.md | 2 +- tensorflow/docs_src/install/install_go.md | 2 +- tensorflow/docs_src/install/install_java.md | 22 +++++++++---------- tensorflow/docs_src/install/install_linux.md | 22 +++++++++---------- tensorflow/docs_src/install/install_mac.md | 10 ++++----- .../docs_src/install/install_sources.md | 9 ++++++-- tensorflow/tools/docker/Dockerfile.devel | 2 +- .../tools/docker/Dockerfile.devel-cpu-mkl | 2 +- tensorflow/tools/docker/Dockerfile.devel-gpu | 2 +- tensorflow/tools/pip_package/setup.py | 2 +- 11 files changed, 42 insertions(+), 37 deletions(-) diff --git a/tensorflow/core/public/version.h b/tensorflow/core/public/version.h index 22f2c02b78..15082bb337 100644 --- a/tensorflow/core/public/version.h +++ b/tensorflow/core/public/version.h @@ -19,12 +19,12 @@ limitations under the License. // TensorFlow uses semantic versioning, see http://semver.org/. #define TF_MAJOR_VERSION 1 -#define TF_MINOR_VERSION 6 +#define TF_MINOR_VERSION 7 #define TF_PATCH_VERSION 0 // TF_VERSION_SUFFIX is non-empty for pre-releases (e.g. "-alpha", "-alpha.1", // "-beta", "-rc", "-rc.1") -#define TF_VERSION_SUFFIX "" +#define TF_VERSION_SUFFIX "-rc0" #define TF_STR_HELPER(x) #x #define TF_STR(x) TF_STR_HELPER(x) diff --git a/tensorflow/docs_src/install/install_c.md b/tensorflow/docs_src/install/install_c.md index 0481c97885..733c7a6625 100644 --- a/tensorflow/docs_src/install/install_c.md +++ b/tensorflow/docs_src/install/install_c.md @@ -38,7 +38,7 @@ enable TensorFlow for C: OS="linux" # Change to "darwin" for macOS TARGET_DIRECTORY="/usr/local" curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.7.0-rc0.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_go.md b/tensorflow/docs_src/install/install_go.md index 8f89898c92..421215f367 100644 --- a/tensorflow/docs_src/install/install_go.md +++ b/tensorflow/docs_src/install/install_go.md @@ -38,7 +38,7 @@ steps to install this library and enable TensorFlow for Go: TF_TYPE="cpu" # Change to "gpu" for GPU support TARGET_DIRECTORY='/usr/local' curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.7.0-rc0.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_java.md b/tensorflow/docs_src/install/install_java.md index 0ee9c849e1..7758520c50 100644 --- a/tensorflow/docs_src/install/install_java.md +++ b/tensorflow/docs_src/install/install_java.md @@ -36,7 +36,7 @@ following to the project's `pom.xml` to use the TensorFlow Java APIs: org.tensorflow tensorflow - 1.6.0 + 1.7.0-rc0 ``` @@ -65,7 +65,7 @@ As an example, these steps will create a Maven project that uses TensorFlow: org.tensorflow tensorflow - 1.6.0 + 1.7.0-rc0 @@ -123,12 +123,12 @@ instead: org.tensorflow libtensorflow - 1.6.0 + 1.7.0-rc0 org.tensorflow libtensorflow_jni_gpu - 1.6.0 + 1.7.0-rc0 ``` @@ -147,7 +147,7 @@ refer to the simpler instructions above instead. Take the following steps to install TensorFlow for Java on Linux or macOS: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc0.jar), which is the TensorFlow Java Archive (JAR). 2. Decide whether you will run TensorFlow for Java on CPU(s) only or with @@ -166,7 +166,7 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: OS=$(uname -s | tr '[:upper:]' '[:lower:]') mkdir -p ./jni curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.7.0-rc0.tar.gz" | tar -xz -C ./jni ### Install on Windows @@ -174,10 +174,10 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: Take the following steps to install TensorFlow for Java on Windows: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc0.jar), which is the TensorFlow Java Archive (JAR). 2. Download the following Java Native Interface (JNI) file appropriate for - [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.6.0.zip). + [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.7.0-rc0.zip). 3. Extract this .zip file. @@ -225,7 +225,7 @@ must be part of your `classpath`. For example, you can include the downloaded `.jar` in your `classpath` by using the `-cp` compilation flag as follows: -
javac -cp libtensorflow-1.6.0.jar HelloTF.java
+
javac -cp libtensorflow-1.7.0-rc0.jar HelloTF.java
### Running @@ -239,11 +239,11 @@ two files are available to the JVM: For example, the following command line executes the `HelloTF` program on Linux and macOS X: -
java -cp libtensorflow-1.6.0.jar:. -Djava.library.path=./jni HelloTF
+
java -cp libtensorflow-1.7.0-rc0.jar:. -Djava.library.path=./jni HelloTF
And the following command line executes the `HelloTF` program on Windows: -
java -cp libtensorflow-1.6.0.jar;. -Djava.library.path=jni HelloTF
+
java -cp libtensorflow-1.7.0-rc0.jar;. -Djava.library.path=jni HelloTF
If the program prints Hello from version, you've successfully installed TensorFlow for Java and are ready to use the API. If the program diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index 3e8744bf9d..f4d4e65548 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -189,7 +189,7 @@ Take the following steps to install TensorFlow with Virtualenv: Virtualenv environment:
(tensorflow)$ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl If you encounter installation problems, see [Common Installation Problems](#common_installation_problems). @@ -294,7 +294,7 @@ take the following steps:
      $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
      
If this step fails, see @@ -480,7 +480,7 @@ Take the following steps to install TensorFlow in an Anaconda environment:
      (tensorflow)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl ## Validate your installation @@ -647,14 +647,14 @@ This section documents the relevant values for Linux installations. CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp27-none-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp27-none-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -666,14 +666,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -685,14 +685,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp35-cp35m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp35-cp35m-linux_x86_64.whl
 
@@ -704,14 +704,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp36-cp36m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp36-cp36m-linux_x86_64.whl
 
diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index 94defcd18c..055a463718 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -119,7 +119,7 @@ Take the following steps to install TensorFlow with Virtualenv: TensorFlow in the active Virtualenv is as follows:
 $ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py3-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl If you encounter installation problems, see [Common Installation Problems](#common-installation-problems). @@ -242,7 +242,7 @@ take the following steps: issue the following command:
 $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py3-none-any.whl 
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl If the preceding command fails, see [installation problems](#common-installation-problems). @@ -350,7 +350,7 @@ Take the following steps to install TensorFlow in an Anaconda environment: TensorFlow for Python 2.7:
 (targetDirectory)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py2-none-any.whl @@ -523,7 +523,7 @@ This section documents the relevant values for Mac OS installations.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py2-none-any.whl
 
@@ -531,5 +531,5 @@ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py3-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl
 
diff --git a/tensorflow/docs_src/install/install_sources.md b/tensorflow/docs_src/install/install_sources.md index c09c9c2c0c..10840295f9 100644 --- a/tensorflow/docs_src/install/install_sources.md +++ b/tensorflow/docs_src/install/install_sources.md @@ -359,10 +359,10 @@ Invoke `pip install` to install that pip package. The filename of the `.whl` file depends on your platform. For example, the following command will install the pip package -for TensorFlow 1.6.0 on Linux: +for TensorFlow 1.7.0rc0 on Linux:
-$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.6.0-py2-none-any.whl
+$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc0-py2-none-any.whl
 
## Validate your installation @@ -459,6 +459,8 @@ Stack Overflow and specify the `tensorflow` tag. **Linux** + + @@ -478,6 +480,7 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
+ @@ -492,6 +495,8 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
+ + diff --git a/tensorflow/tools/docker/Dockerfile.devel b/tensorflow/tools/docker/Dockerfile.devel index 22c73c3fe1..11f476d12c 100644 --- a/tensorflow/tools/docker/Dockerfile.devel +++ b/tensorflow/tools/docker/Dockerfile.devel @@ -70,7 +70,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # TODO(craigcitro): Don't install the pip package, since it makes it # more difficult to experiment with local changes. Instead, just add diff --git a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl index 3690e7dfe5..037d13116e 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl +++ b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl @@ -3,7 +3,7 @@ FROM tensorflow/tensorflow:latest-devel LABEL maintainer="Clayne Robison" # These arguments are parameterized. Use --build-args to override. -ARG TF_BRANCH=r1.6 +ARG TF_BRANCH=r1.7 ARG WHL_DIR=/whl RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/tensorflow/tools/docker/Dockerfile.devel-gpu b/tensorflow/tools/docker/Dockerfile.devel-gpu index 69ba340f92..1fcb6428b2 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-gpu +++ b/tensorflow/tools/docker/Dockerfile.devel-gpu @@ -79,7 +79,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 815ea8157d..69825a0d7c 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.6.0' +_VERSION = '1.7.0-rc0' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', -- GitLab From e8cf1fb7dc9dabe1a2a0b181a7b587c1300888a3 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Thu, 8 Mar 2018 14:07:30 -0800 Subject: [PATCH 016/906] Use getfullargspec in signature parsing. --- tensorflow/python/util/tf_inspect.py | 36 ++++++++++++++++++++++------ tensorflow/tools/docs/parser.py | 34 +++++++++++++------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/tensorflow/python/util/tf_inspect.py b/tensorflow/python/util/tf_inspect.py index c4168f7b1a..1fbc33ba0b 100644 --- a/tensorflow/python/util/tf_inspect.py +++ b/tensorflow/python/util/tf_inspect.py @@ -18,12 +18,22 @@ from __future__ import division from __future__ import print_function import inspect as _inspect +import six +from collections import namedtuple from tensorflow.python.util import tf_decorator ArgSpec = _inspect.ArgSpec +if six.PY3: + FullArgSpec = _inspect.FullArgSpec +else: + FullArgSpec = namedtuple( + 'FullArgSpec', ['args', 'varargs', 'varkw', 'defaults', + 'kwonlyargs', 'kwonlydefaults', 'annotations']) + + def currentframe(): """TFDecorator-aware replacement for inspect.currentframe.""" return _inspect.stack()[1][0] @@ -46,20 +56,32 @@ def getargspec(object): # pylint: disable=redefined-builtin def getfullargspec(obj): # pylint: disable=redefined-builtin - """TFDecorator-aware replacement for inspect.getfullargspec and fallback to - inspect.getargspec in Python 2. + """TFDecorator-aware replacement for inspect.getfullargspec. Args: obj: A callable, possibly decorated. Returns: - The `FullArgSpec` (`ArgSpec` in Python 2) that describes the signature of + The `FullArgSpec` that describes the signature of the outermost decorator that changes the callable's signature. If the - callable is not decorated, `inspect.getfullargspec()` - (`inspect.getargspec()` in Python 2) will be called directly on the - callable. + callable is not decorated, `inspect.getfullargspec()` will be called + directly on the callable. """ - spec_fn = getattr(_inspect, 'getfullargspec', getattr(_inspect, 'getargspec')) + if six.PY2: + def spec_fn(target): + argspecs = _inspect.getargspec(target) + fullargspecs = FullArgSpec( + args=argspecs.args, + varargs=argspecs.varargs, + varkw=argspecs.keywords, + defaults=argspecs.defaults, + kwonlyargs=[], + kwonlydefaults={}, + annotations={}) + return fullargspecs + else: + spec_fn = _inspect.getfullargspec + decorators, target = tf_decorator.unwrap(obj) return next((d.decorator_argspec for d in decorators if d.decorator_argspec is not None), spec_fn(target)) diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index dd0351b4c6..16513d0ee1 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -601,20 +601,20 @@ def _parse_md_docstring(py_object, relative_path_to_root, reference_resolver): def _get_arg_spec(func): """Extracts signature information from a function or functools.partial object. - For functions, uses `tf_inspect.getargspec`. For `functools.partial` objects, - corrects the signature of the underlying function to take into account the - removed arguments. + For functions, uses `tf_inspect.getfullargspec`. For `functools.partial` + objects, corrects the signature of the underlying function to take into + account the removed arguments. Args: func: A function whose signature to extract. Returns: - An `ArgSpec` namedtuple `(args, varargs, keywords, defaults)`, as returned - by `tf_inspect.getargspec`. + An `FullArgSpec` namedtuple `(args, varargs, varkw, defaults, etc.)`, + as returned by `tf_inspect.getfullargspec`. """ - # getargspec does not work for functools.partial objects directly. + # getfullargspec does not work for functools.partial objects directly. if isinstance(func, functools.partial): - argspec = tf_inspect.getargspec(func.func) + argspec = tf_inspect.getfullargspec(func.func) # Remove the args from the original function that have been used up. first_default_arg = ( len(argspec.args or []) - len(argspec.defaults or [])) @@ -637,12 +637,14 @@ def _get_arg_spec(func): argspec_defaults.pop(i-first_default_arg) else: first_default_arg -= 1 - return tf_inspect.ArgSpec(args=argspec_args, - varargs=argspec.varargs, - keywords=argspec.keywords, - defaults=tuple(argspec_defaults)) + # NOTE Some fields from FullArgSpec were removed here. + # Add them back if needed in the future. + return tf_inspect.FullArgSpec(args=argspec_args, + varargs=argspec.varargs, + varkw=argspec.varkw, + defaults=tuple(argspec_defaults)) else: # Regular function or method, getargspec will work fine. - return tf_inspect.getargspec(func) + return tf_inspect.getfullargspec(func) def _remove_first_line_indent(string): @@ -657,7 +659,7 @@ def _generate_signature(func, reverse_index): """Given a function, returns a list of strings representing its args. This function produces a list of strings representing the arguments to a - python function. It uses tf_inspect.getargspec, which + python function. It uses tf_inspect.getfullargspec, which does not generalize well to Python 3.x, which is more flexible in how *args and **kwargs are handled. This is not a problem in TF, since we have to remain compatible to Python 2.7 anyway. @@ -710,7 +712,7 @@ def _generate_signature(func, reverse_index): elif ast_default is not None: default_text = ( astor.to_source(ast_default).rstrip('\n').replace('\t', '\\t') - .replace('\n', '\\n').replace('"""', "'")) + .replace('\n', '\\n').replace('"""', "'")) default_text = PAREN_NUMBER_RE.sub('\\1', default_text) if default_text != repr(default): @@ -745,8 +747,8 @@ def _generate_signature(func, reverse_index): # Add *args and *kwargs. if argspec.varargs: args_list.append('*' + argspec.varargs) - if argspec.keywords: - args_list.append('**' + argspec.keywords) + if argspec.varkw: + args_list.append('**' + argspec.varkw) return args_list -- GitLab From 8cf2a1f0db40174cd6feab96c07e47ba8349d11c Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Thu, 8 Mar 2018 14:18:54 -0800 Subject: [PATCH 017/906] fix encoding again --- tensorflow/tools/docs/generate_lib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index a7ab0fa538..d9e8069a61 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -133,8 +133,12 @@ def write_docs(output_dir, parser_config, yaml_toc, root_title='TensorFlow'): try: if not os.path.exists(directory): os.makedirs(directory) + # This function returns raw bytes in PY2 or unicode in PY3. + text = pretty_docs.build_md_page(page_info) + if six.PY3: + text = text.encode('utf-8') with open(path, 'wb') as f: - f.write(pretty_docs.build_md_page(page_info).encode('utf-8')) + f.write(text) except OSError as e: print('Cannot write documentation for %s to %s: %s' % (full_name, directory, e)) -- GitLab From 2a849d5c1fda91c7cbb16786354d5143519da650 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Thu, 8 Mar 2018 17:37:33 -0800 Subject: [PATCH 018/906] Disable checkpointable_utils_test failed http://ci.tensorflow.org/view/Release/job/release-debian-cpu/99/consoleFull --- tensorflow/contrib/eager/python/BUILD | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD index 5a6251b871..fad833dd2d 100644 --- a/tensorflow/contrib/eager/python/BUILD +++ b/tensorflow/contrib/eager/python/BUILD @@ -266,7 +266,10 @@ cuda_py_test( "//tensorflow/python/eager:test", "//tensorflow/python/keras", ], - tags = ["no_windows"], # TODO: needs investigation on Windows + tags = [ + "no_oss", # b/74395663 + "no_windows", # TODO: needs investigation on Windows + ], ) filegroup( -- GitLab From b006115403f4a6592dee630132b0cf9c6519a922 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Thu, 8 Mar 2018 19:54:14 -0800 Subject: [PATCH 019/906] Make spinn_test less flaky (#17580) --- tensorflow/contrib/eager/python/examples/spinn/spinn_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py index 081b0af14f..3f9a7818a5 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py +++ b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py @@ -417,7 +417,6 @@ class SpinnTest(test_util.TensorFlowTestCase): if event.summary.value and event.summary.value[0].tag == "train/loss"] self.assertEqual(config.epochs, len(train_losses)) - self.assertLess(train_losses[-1], train_losses[0]) # 5. Verify that checkpoints exist and contains all the expected variables. self.assertTrue(glob.glob(os.path.join(config.logdir, "ckpt*"))) -- GitLab From 32584800fe9032396713baf413914ddd391152dc Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Fri, 9 Mar 2018 00:14:49 -0800 Subject: [PATCH 020/906] Hide os from docs generator (#17577) Hide os so we don't generate api_docs for it --- tensorflow/contrib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/__init__.py b/tensorflow/contrib/__init__.py index bcf0d7b48b..669d611b01 100644 --- a/tensorflow/contrib/__init__.py +++ b/tensorflow/contrib/__init__.py @@ -95,6 +95,7 @@ from tensorflow.contrib.summary import summary from tensorflow.python.util.lazy_loader import LazyLoader ffmpeg = LazyLoader("ffmpeg", globals(), "tensorflow.contrib.ffmpeg") +del os del LazyLoader del absolute_import -- GitLab From 4638dd1923055b9aa80ec643c1ccc3a78e41069a Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Fri, 9 Mar 2018 10:19:13 -0800 Subject: [PATCH 021/906] Fix pylint error in single_return.py --- tensorflow/contrib/py2tf/converters/single_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/py2tf/converters/single_return.py b/tensorflow/contrib/py2tf/converters/single_return.py index 90bc22008f..1194b98f5e 100644 --- a/tensorflow/contrib/py2tf/converters/single_return.py +++ b/tensorflow/contrib/py2tf/converters/single_return.py @@ -212,7 +212,7 @@ class DetectReturnInUnsupportedControlFlow(gast.NodeVisitor): def __init__(self): self.cant_return = False - super(gast.NodeVisitor, self).__init__() + super(DetectReturnInUnsupportedControlFlow, self).__init__() def visit_While(self, node): self.cant_return = True -- GitLab From b8f4e763171dcab40defcee1a981c3d2d32aaeca Mon Sep 17 00:00:00 2001 From: Amit Patankar Date: Fri, 9 Mar 2018 10:54:37 -0800 Subject: [PATCH 022/906] Adding the new variables to path rather than overriding them. --- tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh index e1b56b9a25..7d471b4703 100755 --- a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh +++ b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh @@ -31,5 +31,5 @@ export TF_NEED_OPENCL_SYCL=0 export TF_NEED_MKL=0 export COMPUTECPP_PATH="/usr/local" -export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" build_libtensorflow_tarball "-cpu-darwin-$(uname -m)" -- GitLab From 41b5fd15e72756dd6ee3a3395db306f107f1e628 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Fri, 9 Mar 2018 11:09:13 -0800 Subject: [PATCH 023/906] Disable tensorflow/contrib/learn:monitors_test for pip gpu --- tensorflow/contrib/learn/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/learn/BUILD b/tensorflow/contrib/learn/BUILD index 895f70eecf..cc69678a2d 100644 --- a/tensorflow/contrib/learn/BUILD +++ b/tensorflow/contrib/learn/BUILD @@ -227,6 +227,7 @@ py_test( size = "small", srcs = ["python/learn/monitors_test.py"], srcs_version = "PY2AND3", + tags = ["no_pip_gpu"], # b/74437598 deps = [ ":learn", "//tensorflow/contrib/framework:framework_py", -- GitLab From 8ed55f4d54fbc85e2cd605aa6540b2fb5909500d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Mon, 12 Mar 2018 10:43:32 +0100 Subject: [PATCH 024/906] Change to tf.DType --- tensorflow/python/ops/math_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index e315a09ea9..c095be2aaf 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -775,7 +775,7 @@ def cast(x, dtype, name=None): x = ops.convert_to_tensor(x, name="x") if x.dtype.base_dtype != base_type: x = gen_math_ops.cast(x, base_type, name=name) - if x.dtype.is_complex and dtype.is_floating: + if x.dtype.is_complex and base_type.is_floating: logging.warn("Casting complex to real discards imaginary part.") return x -- GitLab From 3abebb0618cb6f830f5afaf2cd0b8c938e584aad Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Mon, 12 Mar 2018 11:36:17 -0700 Subject: [PATCH 025/906] Update RELEASE.md for r1.7 (#17583) --- RELEASE.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index 6f54dee58f..c63d9f20c9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,63 @@ +# Release 1.7.0 + +## Major Features And Improvements +* Eager mode is moving out of contrib, try `tf.enable_eager_execution()`. +* Graph rewrites emulating fixed-point quantization compatible with TensorFlow Lite, supported by new `tf.contrib.quantize` package. +* Easily customize gradient computation with `tf.custom_gradient`. +* [TensorBoard Debugger Plugin](https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/debugger/README.md), the graphical user interface (GUI) of TensorFlow Debugger (tfdbg), is now in alpha. +* Experimental support for reading a sqlite database as a `Dataset` with new `tf.contrib.data.SqlDataset`. +* Distributed Mutex / CriticalSection added to `tf.contrib.framework.CriticalSection`. +* Better text processing with `tf.regex_replace`. +* Easy, efficient sequence input with `tf.contrib.data.bucket_by_sequence_length` + +## Bug Fixes and Other Changes +* Accelerated Linear Algebra (XLA): + * Add `MaxPoolGradGrad` support for XLA + * CSE pass from Tensorflow is now disabled in XLA. +* `tf.data`: + * `tf.data.Dataset` + * Add support for building C++ Dataset op kernels as external libraries, using the `tf.load_op_library()` mechanism. + * `Dataset.list_files()` now shuffles its output by default. + * `Dataset.shuffle(..., seed=tf.constant(0, dtype=tf.int64))` now yields the same sequence of elements as `Dataset.shuffle(..., seed=0)`. + * Add `num_parallel_reads` argument to `tf.data.TFRecordDataset`. +* `tf.contrib`: + * `tf.contrib.bayesflow.halton_sequence` now supports randomization. + * Add support for scalars in `tf.contrib.all_reduce`. + * Add `effective_sample_size` to `tf.contrib.bayesflow.mcmc_diagnostics`. + * Add `potential_scale_reduction` to `tf.contrib.bayesflow.mcmc_diagnostics`. + * Add `BatchNormalization`, `Kumaraswamy` bijectors. + * Deprecate `tf.contrib.learn`. Please check contrib/learn/README.md for instructions on how to convert existing code. + * `tf.contrib.data` + * Remove deprecated `tf.contrib.data.Dataset`, `tf.contrib.data.Iterator`, `tf.contrib.data.FixedLengthRecordDataset`, `tf.contrib.data.TextLineDataset`, and `tf.contrib.data.TFRecordDataset` classes. + * Added `bucket_by_sequence_length`, `sliding_window_batch`, and `make_batched_features_dataset` + * Remove unmaintained `tf.contrib.ndlstm`. You can find it externally at https://github.com/tmbarchive/tfndlstm. + * Moved most of `tf.contrib.bayesflow` to its own repo: `tfp` +* Other: + * tf.py_func now reports the full stack trace if an exception occurs. + * Integrate `TPUClusterResolver` with GKE's integration for Cloud TPUs. + * Add a library for statistical testing of samplers. + * Add Helpers to stream data from the GCE VM to a Cloud TPU. + * Integrate ClusterResolvers with TPUEstimator. + * Unify metropolis_hastings interface with HMC kernel. + * Move LIBXSMM convolutions to a separate --define flag so that they are disabled by default. + * Fix `MomentumOptimizer` lambda. + * Reduce `tfp.layers` boilerplate via programmable docstrings. + * Add `auc_with_confidence_intervals`, a method for computing the AUC and confidence interval with linearithmic time complexity. + * `regression_head` now accepts customized link function, to satisfy the usage that user can define their own link function if the `array_ops.identity` does not meet the requirement. + * Fix `initialized_value` and `initial_value` behaviors for `ResourceVariables` created from `VariableDef` protos. + * Add TensorSpec to represent the specification of Tensors. + * Constant folding pass is now deterministic. + * Support `float16` `dtype` in `tf.linalg.*`. + * Add `tf.estimator.export.TensorServingInputReceiver` that allows `tf.estimator.Estimator.export_savedmodel` to pass raw tensors to model functions. + +## Thanks to our Contributors + +This release contains contributions from many people at Google, as well as: + +4d55397500, Abe, Alistair Low, Andy Kernahan, Appledore, Ben, Ben Barsdell, Boris Pfahringer, Brad Wannow, Brett Koonce, Carl Thomé, cclauss, Chengzhi Chen, Chris Drake, Christopher Yeh, Clayne Robison, Codrut Grosu, Daniel Trebbien, Danny Goodman, David Goodwin, David Norman, Deron Eriksson, Donggeon Lim, Donny Viszneki, DosLin, DylanDmitri, Francisco Guerrero, Fred Reiss, gdh1995, Giuseppe, Glenn Weidner, gracehoney, Guozhong Zhuang, Haichen "Hc" Li, Harald Husum, harumitsu.nobuta, Henry Spivey, hsm207, Jekyll Song, Jerome, Jiongyan Zhang, jjsjann123, John Sungjin Park, Johnson145, JoshVarty, Julian Wolff, Jun Wang, June-One, Kamil Sindi, Kb Sriram, Kdavis-Mozilla, Kenji, lazypanda1, Liang-Chi Hsieh, Loo Rong Jie, Mahesh Bhosale, MandarJKulkarni, ManHyuk, Marcus Ong, Marshal Hayes, Martin Pool, matthieudelaro, mdfaijul, mholzel, Michael Zhou, Ming Li, Minmin Sun, Myungjoo Ham, MyungsungKwak, Naman Kamra, Peng Yu, Penghao Cen, Phil, Raghuraman-K, resec, Rohin Mohanadas, Sandeep N Gupta, Scott Tseng, seaotterman, Seo Sanghyeon, Sergei Lebedev, Ted Chang, terrytangyuan, Tim H, tkunic, Tod, vihanjain, Yan Facai (颜发才), Yin Li, Yong Tang, Yukun Chen, Yusuke Yamada + + + # Release 1.6.0 ## Breaking Changes -- GitLab From 66b38c5e7af4b607f393973d18aaabb6e00f9723 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Mon, 12 Mar 2018 12:56:59 -0700 Subject: [PATCH 026/906] Block docs for str, repr, hash. No python2 code is generating useful docs for these, and in python3 many useless docs are generated, so I've blocked them. --- tensorflow/tools/docs/parser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index 5f2a411bae..95155b1149 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -1127,7 +1127,8 @@ class _ClassPageInfo(object): # Remove builtin members that we never want to document. if short_name in ['__class__', '__base__', '__weakref__', '__doc__', '__module__', '__dict__', '__abstractmethods__', - '__slots__', '__getnewargs__']: + '__slots__', '__getnewargs__', '__str__', + '__repr__', '__hash__']: continue child_name = '.'.join([self.full_name, short_name]) @@ -1172,7 +1173,7 @@ class _ClassPageInfo(object): # obvious what they do, don't include them in the docs if there's no # docstring. if not child_doc.brief.strip() and short_name in [ - '__str__', '__repr__', '__hash__', '__del__', '__copy__']: + '__del__', '__copy__']: print('Skipping %s, defined in %s, no docstring.' % (child_name, defining_class)) continue -- GitLab From d392b1c9ebf131b9ac64ff289d26e43afea21c10 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Fri, 9 Mar 2018 18:17:43 -0800 Subject: [PATCH 027/906] Fix the windows build --- tensorflow/core/kernels/snapshot_op.cc | 30 +++++++++++++++++++ tensorflow/core/kernels/snapshot_op.h | 26 +++++----------- tensorflow/core/kernels/snapshot_op_gpu.cu.cc | 10 +++---- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/tensorflow/core/kernels/snapshot_op.cc b/tensorflow/core/kernels/snapshot_op.cc index 50157d5d48..fe04dcf72e 100644 --- a/tensorflow/core/kernels/snapshot_op.cc +++ b/tensorflow/core/kernels/snapshot_op.cc @@ -22,6 +22,26 @@ limitations under the License. namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; +typedef Eigen::GpuDevice GPUDevice; + +template +class SnapshotOp : public OpKernel { + public: + explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + const Tensor& input = context->input(0); + Tensor* output = nullptr; + // Try to use buffer forwarding to avoid an explicit copy. + OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( + {0}, 0, input.shape(), &output)); + if (!output->SharesBufferWith(input)) { + functor::Snapshot functor; + functor(context->eigen_device(), input.flat(), + output->flat()); + } + } +}; #define REGISTER_KERNEL(TYPE) \ REGISTER_KERNEL_BUILDER( \ @@ -31,6 +51,16 @@ typedef Eigen::ThreadPoolDevice CPUDevice; TF_CALL_POD_TYPES(REGISTER_KERNEL); #undef REGISTER_KERNEL +#if GOOGLE_CUDA +#define REGISTER_KERNEL(TYPE) \ + REGISTER_KERNEL_BUILDER( \ + Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ + SnapshotOp); + +TF_CALL_POD_TYPES(REGISTER_KERNEL); +#undef REGISTER_KERNEL +#endif + #if TENSORFLOW_USE_SYCL typedef Eigen::SyclDevice SyclDevice; #define REGISTER_SYCL_KERNEL(TYPE) \ diff --git a/tensorflow/core/kernels/snapshot_op.h b/tensorflow/core/kernels/snapshot_op.h index b94834f159..a18065d42b 100644 --- a/tensorflow/core/kernels/snapshot_op.h +++ b/tensorflow/core/kernels/snapshot_op.h @@ -26,29 +26,19 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" namespace tensorflow { +namespace functor { +// Functor used by SnapshotOp. template -class SnapshotOp : public OpKernel { - public: - explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} - - void Compute(OpKernelContext* context) override { - const Tensor& input = context->input(0); - Tensor* output = nullptr; - // Try to use buffer forwarding to avoid an explicit copy. - OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 0, input.shape(), &output)); - if (!output->SharesBufferWith(input)) { - // We had to allocate a new buffer since the refcount on the input was - // greater than 1. Copy the input to the new buffer. - const Device& device = context->eigen_device(); - device.memcpy(output->template flat().data(), - input.template flat().data(), - input.NumElements() * sizeof(Scalar)); - } +struct Snapshot { + void operator()(const Device& device, + typename TTypes::ConstTensor input, + typename TTypes::Tensor output) { + device.memcpy(output.data(), input.data(), input.size() * sizeof(Scalar)); } }; +} // namespace functor } // namespace tensorflow #endif // TENSORFLOW_KERNELS_SNAPSHOT_OP_H_ diff --git a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc index 52070be838..f1c0ed2eae 100644 --- a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc +++ b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc @@ -24,13 +24,11 @@ limitations under the License. namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; -#define REGISTER_KERNEL(TYPE) \ - REGISTER_KERNEL_BUILDER( \ - Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ - SnapshotOp); +// Definition of the GPU implementations declared in softsign_op.cc. +#define DEFINE_GPU_KERNELS(T) \ + template struct functor::Snapshot; -TF_CALL_POD_TYPES(REGISTER_KERNEL); -#undef REGISTER_KERNEL +TF_CALL_POD_TYPES(DEFINE_GPU_KERNELS); } // namespace tensorflow -- GitLab From 73f2da07577330648cd294d321545d089b600748 Mon Sep 17 00:00:00 2001 From: Yifei Feng Date: Sat, 10 Mar 2018 22:24:27 -0800 Subject: [PATCH 028/906] Disable keras:convolutional_test. --- tensorflow/contrib/cmake/tf_tests.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/contrib/cmake/tf_tests.cmake b/tensorflow/contrib/cmake/tf_tests.cmake index 1c4ebd7f0c..e2ed5f6c73 100644 --- a/tensorflow/contrib/cmake/tf_tests.cmake +++ b/tensorflow/contrib/cmake/tf_tests.cmake @@ -208,6 +208,9 @@ if (tensorflow_BUILD_PYTHON_TESTS) "${tensorflow_source_dir}/tensorflow/contrib/learn/python/learn/learn_io/graph_io_test.py" # Test is flaky on Windows GPU builds (b/38283730). "${tensorflow_source_dir}/tensorflow/contrib/factorization/python/ops/gmm_test.py" + # Disable following manual tag in BUILD. + "${tensorflow_source_dir}/tensorflow/python/keras/_impl/keras/layers/convolutional_test.py" + ) if (WIN32) set(tf_test_src_py_exclude -- GitLab From df4cbfa33d711c1fad107bfaea0862bfdc8c3fd8 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Mon, 12 Mar 2018 16:49:40 -0700 Subject: [PATCH 029/906] Make tensorflow/python:framework_importer_test large tensorflow/python:framework_importer_test sometime times out during release builds --- tensorflow/python/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 04e926ff16..6dd53ffdf6 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -1048,7 +1048,7 @@ py_test( py_test( name = "framework_importer_test", - size = "medium", + size = "large", srcs = ["framework/importer_test.py"], main = "framework/importer_test.py", srcs_version = "PY2AND3", -- GitLab From fa5c66ba74a505e4a4b8472332918798bb17bb39 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Sun, 11 Mar 2018 15:38:16 -0700 Subject: [PATCH 030/906] Fixes a race condition in function instantiation. Previously, if the same function was being concurrently instantiated and released: 1. Thread one could begin to instantiate the function, determine that it already existed in the runtime, then be preempted. 2. Thread two could release the handle on the function, causing it to be freed and removed from the `FunctionLibraryRuntime::items_` map. 3. Thread one could then incorrectly assume that the function still existed, and fail to find it in the `FunctionLibraryRuntime::items_` map, causing a segfault when it attempted to increment the refcount on an uninitialized object. PiperOrigin-RevId: 188661500 --- tensorflow/core/common_runtime/function.cc | 24 +++++++++++++++---- .../kernel_tests/filter_dataset_op_test.py | 8 +++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/common_runtime/function.cc b/tensorflow/core/common_runtime/function.cc index 3e937ceb64..7174a876f6 100644 --- a/tensorflow/core/common_runtime/function.cc +++ b/tensorflow/core/common_runtime/function.cc @@ -479,11 +479,26 @@ Status FunctionLibraryRuntimeImpl::Instantiate( InstantiateOptions options_copy(options); options_copy.target = device_name_; const string key = Canonicalize(function_name, attrs, options_copy); - *handle = parent_->GetHandle(key); - if (*handle != kInvalidHandle) { + + { mutex_lock l(mu_); - items_[parent_->GetHandleOnDevice(device_name_, *handle)]->Ref(); - return Status::OK(); + *handle = parent_->GetHandle(key); + if (*handle != kInvalidHandle) { + FunctionLibraryRuntime::LocalHandle handle_on_device = + parent_->GetHandleOnDevice(device_name_, *handle); + if (handle_on_device == kInvalidLocalHandle) { + return errors::Internal("LocalHandle not found for handle ", *handle, + "."); + } + auto item_handle = items_.find(handle_on_device); + if (item_handle == items_.end()) { + return errors::Internal("LocalHandle ", handle_on_device, + " for handle ", *handle, + " not found in items."); + } + item_handle->second->Ref(); + return Status::OK(); + } } Status s; @@ -536,6 +551,7 @@ Status FunctionLibraryRuntimeImpl::ReleaseHandle(Handle handle) { } LocalHandle h = parent_->GetHandleOnDevice(device_name_, handle); + CHECK_NE(h, kInvalidLocalHandle); mutex_lock l(mu_); CHECK_EQ(1, items_.count(h)); Item* item = items_[h]; diff --git a/tensorflow/python/data/kernel_tests/filter_dataset_op_test.py b/tensorflow/python/data/kernel_tests/filter_dataset_op_test.py index 2c71723167..4f2216f0a3 100644 --- a/tensorflow/python/data/kernel_tests/filter_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/filter_dataset_op_test.py @@ -176,6 +176,14 @@ class FilterDatasetTest(test.TestCase): with self.assertRaises(errors.OutOfRangeError): sess.run(get_next) + def testParallelFilters(self): + dataset = dataset_ops.Dataset.range(10).filter( + lambda x: math_ops.equal(x % 2, 0)) + iterators = [dataset.make_one_shot_iterator() for _ in range(10)] + next_elements = [iterator.get_next() for iterator in iterators] + with self.test_session() as sess: + self.assertEqual([0 for _ in range(10)], sess.run(next_elements)) + class FilterDatasetBenchmark(test.Benchmark): -- GitLab From 8198a84e1a584cd3d14acd0bd52e04cf2d66f341 Mon Sep 17 00:00:00 2001 From: Sami Kama Date: Tue, 13 Mar 2018 16:38:08 -0700 Subject: [PATCH 031/906] Update documentation --- tensorflow/contrib/tensorrt/README.md | 46 +++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/tensorrt/README.md b/tensorflow/contrib/tensorrt/README.md index 461e627e99..6eafc1754c 100644 --- a/tensorflow/contrib/tensorrt/README.md +++ b/tensorflow/contrib/tensorrt/README.md @@ -1,15 +1,15 @@ -Using TensorRT in TensorFlow -============================ +# Using TensorRT in TensorFlow + This module provides necessary bindings and introduces TRT_engine_op operator that wraps a subgraph in TensorRT. This is still a work in progress but should be useable with most common graphs. -Compilation ------------ +## Compilation + In order to compile the module, you need to have a local TensorRT -installation (libnvinfer.so and respective include files). During the +installation ( libnvinfer.so and respective include files ). During the configuration step, TensorRT should be enabled and installation path should be set. If installed through package managers (deb,rpm), configure script should find the necessary components from the system @@ -22,4 +22,38 @@ bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/ ``` After the installation of tensorflow package, TensorRT transformation -will be available. An example use can be found in test/test_tftrt.py directory +will be available. An example use can be found in test/test_tftrt.py script + +## Installing TensorRT 3.0.4 + +In order to make use of TensorRT integration, you will need a local installation of TensorRT 3.0.4 from the [NVIDIA Developer website](https://developer.nvidia.com/tensorrt). Due to compiler compatibility, you will need to download and install the TensorRT 3.0.4 tarball for _Ubuntu 14.04_, i.e., **_TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz_**, even if you are using Ubuntu 16.04 or later. + +### Preparing TensorRT installation + +Once you have downloaded TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz, you will need to unpack it to an installation directory, which will be referred to as . Please replace with the full path of actual installation directory you choose in commands below. + +```shell +cd && tar -zxf /path/to/TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz +``` + +After unpacking the binaries, you have several options to use them: + +#### To run TensorFlow as a user without superuser privileges + +For a regular user without any sudo rights, you should add TensorRT to your `$LD_LIBRARY_PATH`: + + ```shell + export LD_LIBRARY_PATH=/TensorRT-3.0.4/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + ``` + +Then you are ready to use TensorFlow-TensorRT integration. `$LD_LIBRARY_PATH` must contain the path to TensorRT installation for TensorFlow-TensorRT integration to work. If you are using a VirtualEnv-like setup, you can add the command above to your `bin/activate` script or to your `.bashrc` script. + +#### To run TensorFlow as a superuser + + When running as a superuser, such as in a container or via sudo, the `$LD_LIBRARY_PATH` approach above may not work. The following is preferred when the user has superuser privileges: + + ```shell + echo "/TensorRT-3.0.4/lib" | sudo tee /etc/ld.so.conf.d/tensorrt304.conf && sudo ldconfig + ``` + + Please ensure that any existing deb package installation of TensorRT is removed before following these instructions to avoid package conflicts. \ No newline at end of file -- GitLab From 6485bb7029c6d856c7ffa744168a8864ef6c986c Mon Sep 17 00:00:00 2001 From: Tatiana Shpeisman Date: Thu, 15 Mar 2018 22:04:33 -0700 Subject: [PATCH 032/906] MKL DNN: fix the TF1.6 speed issue by fixing MKL DNN LRN taking the optimum path (#17605) (#17751) * MKL DNN: fix the TF1.6 speed issue by fixing MKL DNN LRN * fixed typos in the doc for LrnRewrite --- tensorflow/core/graph/mkl_layout_pass.cc | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 02038c5d77..568fc87e65 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -2492,10 +2492,10 @@ class MklLayoutRewritePass : public GraphOptimizationPass { mkl_op_registry::GetMklOpName(csinfo_.identity), CopyAttrsDataType, AlwaysRewrite}); rinfo_.push_back({csinfo_.lrn, mkl_op_registry::GetMklOpName(csinfo_.lrn), - CopyAttrsLRN, AlwaysRewrite}); + CopyAttrsLRN, LrnRewrite}); rinfo_.push_back({csinfo_.lrn_grad, mkl_op_registry::GetMklOpName(csinfo_.lrn_grad), - CopyAttrsLRN, AlwaysRewrite}); + CopyAttrsLRN, LrnRewrite}); rinfo_.push_back({csinfo_.max_pool, mkl_op_registry::GetMklOpName(csinfo_.max_pool), CopyAttrsPooling, NonDepthBatchWisePoolRewrite}); @@ -2865,6 +2865,28 @@ class MklLayoutRewritePass : public GraphOptimizationPass { return false; } + // If the depth_radius of LRN is not 2, then MKL DNN takes unoptimized + // path. The unoptimized path is slow. Thus we dont rewrite the node + // and use default Eigen. But for depth_radius=2, MKL DNN optimized + // path is taken, i.e., eigen node is rewritten by MKl DNN node. + static bool LrnRewrite(const Node* n) { + CHECK_NOTNULL(n); + + int depth_radius; + CHECK_EQ(GetNodeAttr(n->def(), "depth_radius", &depth_radius).ok(), true); + + // if the depth_radius of LRN is not 2, don't rewrite the node by MKL DNN + // and use eigen node instead + if (depth_radius == 2) { + return true; + } + VLOG(1) << "LrnRewrite: The model sets depth_radius as not 2 which" + << "case is not optimized by Intel MKL, thus using Eigen op" + << "for LRN " ; + + return false; + } + static bool AddNRewrite(const Node* n) { CHECK_NOTNULL(n); -- GitLab From 6271467df2f0bf7c776f888bed3c6722502efc51 Mon Sep 17 00:00:00 2001 From: Jakub Kolodziejczyk Date: Sat, 17 Mar 2018 01:58:09 +0900 Subject: [PATCH 033/906] Updated tf.Session.run(...) documentation Added information that order in which `fetches` are evaluated inside the call is undefined. Discussion: https://github.com/tensorflow/tensorflow/issues/13133 --- tensorflow/python/client/session.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/python/client/session.py b/tensorflow/python/client/session.py index 29f06c8f22..9b7e853e8c 100644 --- a/tensorflow/python/client/session.py +++ b/tensorflow/python/client/session.py @@ -889,6 +889,8 @@ class BaseSession(SessionInterface): Either a single value if `fetches` is a single graph element, or a list of values if `fetches` is a list, or a dictionary with the same keys as `fetches` if that is a dictionary (described above). + Order in which `fetches` operations are evaluated inside the call + is undefined. Raises: RuntimeError: If this `Session` is in an invalid state (e.g. has been -- GitLab From 6655570f12dba22fe752796471635e109e682056 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Wed, 14 Mar 2018 18:39:25 -0700 Subject: [PATCH 034/906] [tf.data] Fix Python shape inference for `tf.contrib.data.map_and_batch()`. Previously, it would incorrectly report that all batches have the same size, not accounting for the possibility of the last batch being partial. Fixes #17720. PiperOrigin-RevId: 189121488 --- .../python/kernel_tests/batch_dataset_op_test.py | 14 ++++++++++++++ tensorflow/contrib/data/python/ops/batching.py | 3 +-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py index 71dc1c1172..a2da953c7b 100644 --- a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py @@ -387,6 +387,20 @@ class BatchDatasetTest(test.TestCase): def testBatchAndMapDatasetWithParallelBatching(self): return self._testBatchAndMapDatasetHelper(num_parallel_batches=10) + def testMapAndBatchYieldsPartialBatch(self): + iterator = (dataset_ops.Dataset.range(10) + .apply(batching.map_and_batch( + lambda x: array_ops.reshape(x * x, [1]), 4)) + .make_one_shot_iterator()) + self.assertEqual([None, 1], iterator.output_shapes.as_list()) + next_element = iterator.get_next() + with self.test_session() as sess: + self.assertAllEqual([[0], [1], [4], [9]], sess.run(next_element)) + self.assertAllEqual([[16], [25], [36], [49]], sess.run(next_element)) + self.assertAllEqual([[64], [81]], sess.run(next_element)) + with self.assertRaises(errors.OutOfRangeError): + sess.run(next_element) + def testMapAndBatchSparse(self): def _sparse(i): diff --git a/tensorflow/contrib/data/python/ops/batching.py b/tensorflow/contrib/data/python/ops/batching.py index 6eb512dec6..6463d75750 100644 --- a/tensorflow/contrib/data/python/ops/batching.py +++ b/tensorflow/contrib/data/python/ops/batching.py @@ -374,8 +374,7 @@ class _MapAndBatchDataset(dataset_ops.MapDataset): @property def output_shapes(self): return nest.pack_sequence_as(self._output_shapes, [ - tensor_shape.vector(tensor_util.constant_value( - self._batch_size)).concatenate(s) + tensor_shape.vector(None).concatenate(s) for s in nest.flatten(self._output_shapes) ]) -- GitLab From a14114177ffec4f0c8665db60bec8819aade0c81 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Fri, 16 Mar 2018 22:58:50 -0700 Subject: [PATCH 035/906] Update version strings for 1.7.0-rc1 (#17786) --- tensorflow/core/public/version.h | 2 +- tensorflow/docs_src/install/install_c.md | 2 +- tensorflow/docs_src/install/install_go.md | 2 +- tensorflow/docs_src/install/install_java.md | 22 +++++++++---------- tensorflow/docs_src/install/install_linux.md | 22 +++++++++---------- tensorflow/docs_src/install/install_mac.md | 10 ++++----- .../docs_src/install/install_sources.md | 14 ++++++------ tensorflow/tools/pip_package/setup.py | 2 +- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/tensorflow/core/public/version.h b/tensorflow/core/public/version.h index 15082bb337..40eebd1db0 100644 --- a/tensorflow/core/public/version.h +++ b/tensorflow/core/public/version.h @@ -24,7 +24,7 @@ limitations under the License. // TF_VERSION_SUFFIX is non-empty for pre-releases (e.g. "-alpha", "-alpha.1", // "-beta", "-rc", "-rc.1") -#define TF_VERSION_SUFFIX "-rc0" +#define TF_VERSION_SUFFIX "-rc1" #define TF_STR_HELPER(x) #x #define TF_STR(x) TF_STR_HELPER(x) diff --git a/tensorflow/docs_src/install/install_c.md b/tensorflow/docs_src/install/install_c.md index 733c7a6625..9059b3f3b6 100644 --- a/tensorflow/docs_src/install/install_c.md +++ b/tensorflow/docs_src/install/install_c.md @@ -38,7 +38,7 @@ enable TensorFlow for C: OS="linux" # Change to "darwin" for macOS TARGET_DIRECTORY="/usr/local" curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.7.0-rc0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_go.md b/tensorflow/docs_src/install/install_go.md index 421215f367..2e47a6d212 100644 --- a/tensorflow/docs_src/install/install_go.md +++ b/tensorflow/docs_src/install/install_go.md @@ -38,7 +38,7 @@ steps to install this library and enable TensorFlow for Go: TF_TYPE="cpu" # Change to "gpu" for GPU support TARGET_DIRECTORY='/usr/local' curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.7.0-rc0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.7.0-rc1.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_java.md b/tensorflow/docs_src/install/install_java.md index 7758520c50..eff066d200 100644 --- a/tensorflow/docs_src/install/install_java.md +++ b/tensorflow/docs_src/install/install_java.md @@ -36,7 +36,7 @@ following to the project's `pom.xml` to use the TensorFlow Java APIs: org.tensorflow tensorflow - 1.7.0-rc0 + 1.7.0-rc1 ``` @@ -65,7 +65,7 @@ As an example, these steps will create a Maven project that uses TensorFlow: org.tensorflow tensorflow - 1.7.0-rc0 + 1.7.0-rc1 @@ -123,12 +123,12 @@ instead: org.tensorflow libtensorflow - 1.7.0-rc0 + 1.7.0-rc1 org.tensorflow libtensorflow_jni_gpu - 1.7.0-rc0 + 1.7.0-rc1 ``` @@ -147,7 +147,7 @@ refer to the simpler instructions above instead. Take the following steps to install TensorFlow for Java on Linux or macOS: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), which is the TensorFlow Java Archive (JAR). 2. Decide whether you will run TensorFlow for Java on CPU(s) only or with @@ -166,7 +166,7 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: OS=$(uname -s | tr '[:upper:]' '[:lower:]') mkdir -p ./jni curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.7.0-rc0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | tar -xz -C ./jni ### Install on Windows @@ -174,10 +174,10 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: Take the following steps to install TensorFlow for Java on Windows: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), which is the TensorFlow Java Archive (JAR). 2. Download the following Java Native Interface (JNI) file appropriate for - [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.7.0-rc0.zip). + [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.7.0-rc1.zip). 3. Extract this .zip file. @@ -225,7 +225,7 @@ must be part of your `classpath`. For example, you can include the downloaded `.jar` in your `classpath` by using the `-cp` compilation flag as follows: -
javac -cp libtensorflow-1.7.0-rc0.jar HelloTF.java
+
javac -cp libtensorflow-1.7.0-rc1.jar HelloTF.java
### Running @@ -239,11 +239,11 @@ two files are available to the JVM: For example, the following command line executes the `HelloTF` program on Linux and macOS X: -
java -cp libtensorflow-1.7.0-rc0.jar:. -Djava.library.path=./jni HelloTF
+
java -cp libtensorflow-1.7.0-rc1.jar:. -Djava.library.path=./jni HelloTF
And the following command line executes the `HelloTF` program on Windows: -
java -cp libtensorflow-1.7.0-rc0.jar;. -Djava.library.path=jni HelloTF
+
java -cp libtensorflow-1.7.0-rc1.jar;. -Djava.library.path=jni HelloTF
If the program prints Hello from version, you've successfully installed TensorFlow for Java and are ready to use the API. If the program diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index f4d4e65548..378946b459 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -189,7 +189,7 @@ Take the following steps to install TensorFlow with Virtualenv: Virtualenv environment:
(tensorflow)$ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl If you encounter installation problems, see [Common Installation Problems](#common_installation_problems). @@ -294,7 +294,7 @@ take the following steps:
      $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
+     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
      
If this step fails, see @@ -480,7 +480,7 @@ Take the following steps to install TensorFlow in an Anaconda environment:
      (tensorflow)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl ## Validate your installation @@ -647,14 +647,14 @@ This section documents the relevant values for Linux installations. CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp27-none-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp27-none-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -666,14 +666,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -685,14 +685,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
 
@@ -704,14 +704,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
 
diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index 055a463718..fa6951a8f1 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -119,7 +119,7 @@ Take the following steps to install TensorFlow with Virtualenv: TensorFlow in the active Virtualenv is as follows:
 $ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl If you encounter installation problems, see [Common Installation Problems](#common-installation-problems). @@ -242,7 +242,7 @@ take the following steps: issue the following command:
 $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl 
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl If the preceding command fails, see [installation problems](#common-installation-problems). @@ -350,7 +350,7 @@ Take the following steps to install TensorFlow in an Anaconda environment: TensorFlow for Python 2.7:
 (targetDirectory)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py2-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl @@ -523,7 +523,7 @@ This section documents the relevant values for Mac OS installations.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py2-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
 
@@ -531,5 +531,5 @@ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py2-none-a
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc0-py3-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
 
diff --git a/tensorflow/docs_src/install/install_sources.md b/tensorflow/docs_src/install/install_sources.md index 10840295f9..0454c172f8 100644 --- a/tensorflow/docs_src/install/install_sources.md +++ b/tensorflow/docs_src/install/install_sources.md @@ -359,10 +359,10 @@ Invoke `pip install` to install that pip package. The filename of the `.whl` file depends on your platform. For example, the following command will install the pip package -for TensorFlow 1.7.0rc0 on Linux: +for TensorFlow 1.7.0rc1 on Linux:
-$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc0-py2-none-any.whl
+$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc1-py2-none-any.whl
 
## Validate your installation @@ -459,8 +459,8 @@ Stack Overflow and specify the `tensorflow` tag. **Linux**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
- - + + @@ -480,7 +480,7 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc1GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
- + @@ -495,8 +495,8 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
- - + + diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 69825a0d7c..7a3184d64d 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.7.0-rc0' +_VERSION = '1.7.0-rc1' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', -- GitLab From dd2d558714ccbf24e180f075746bdc4bbb745b97 Mon Sep 17 00:00:00 2001 From: Aghasy Date: Sat, 17 Mar 2018 17:27:24 +0400 Subject: [PATCH 036/906] fix nested scope issue --- tensorflow/core/platform/default/logging.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/platform/default/logging.h b/tensorflow/core/platform/default/logging.h index f0efa31d55..2c134f1be9 100644 --- a/tensorflow/core/platform/default/logging.h +++ b/tensorflow/core/platform/default/logging.h @@ -64,11 +64,11 @@ class LogMessageFatal : public LogMessage { }; #define _TF_LOG_INFO \ - ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::INFO) + ::tensorflow::internal::LogMessage(__FILE__, __LINE__, ::tensorflow::INFO) #define _TF_LOG_WARNING \ - ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::WARNING) + ::tensorflow::internal::LogMessage(__FILE__, __LINE__, ::tensorflow::WARNING) #define _TF_LOG_ERROR \ - ::tensorflow::internal::LogMessage(__FILE__, __LINE__, tensorflow::ERROR) + ::tensorflow::internal::LogMessage(__FILE__, __LINE__, ::tensorflow::ERROR) #define _TF_LOG_FATAL \ ::tensorflow::internal::LogMessageFatal(__FILE__, __LINE__) -- GitLab From 2b1b9ea110bcf26f047689564298de43ab83db18 Mon Sep 17 00:00:00 2001 From: imsheridan Date: Mon, 19 Mar 2018 00:34:25 +0800 Subject: [PATCH 037/906] Fix the broken link ofr build the op library in extend tutorials --- tensorflow/docs_src/extend/add_filesys.md | 2 +- tensorflow/docs_src/extend/new_data_formats.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/docs_src/extend/add_filesys.md b/tensorflow/docs_src/extend/add_filesys.md index 06f11de4eb..bc0f662f0c 100644 --- a/tensorflow/docs_src/extend/add_filesys.md +++ b/tensorflow/docs_src/extend/add_filesys.md @@ -225,7 +225,7 @@ it will use the `FooBarFileSystem` implementation. Next, you must build a shared object containing this implementation. An example of doing so using bazel's `cc_binary` rule can be found [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/BUILD#L244), -but you may use any build system to do so. See the section on @{$adding_an_op#build-the-op-library$building the op library} for similar +but you may use any build system to do so. See the section on @{$adding_an_op#build_the_op_library$building the op library} for similar instructions. The result of building this target is a `.so` shared object file. diff --git a/tensorflow/docs_src/extend/new_data_formats.md b/tensorflow/docs_src/extend/new_data_formats.md index b3cc968047..10e717c280 100644 --- a/tensorflow/docs_src/extend/new_data_formats.md +++ b/tensorflow/docs_src/extend/new_data_formats.md @@ -167,7 +167,7 @@ REGISTER_KERNEL_BUILDER(Name("TextLineReader").Device(DEVICE_CPU), ``` The last step is to add the Python wrapper. You can either do this by -@{$adding_an_op#building_the_op_library$compiling a dynamic library} +@{$adding_an_op#build_the_op_library$compiling a dynamic library} or, if you are building TensorFlow from source, adding to `user_ops.py`. For the latter, you will import `tensorflow.python.ops.io_ops` in [`tensorflow/python/user_ops/user_ops.py`](https://www.tensorflow.org/code/tensorflow/python/user_ops/user_ops.py) -- GitLab From c07b18684c3b20dd91911a31bbd6169ad9cc1617 Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Mon, 19 Mar 2018 23:24:53 +0800 Subject: [PATCH 038/906] Fix set_difference doc --- tensorflow/python/ops/sets_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/ops/sets_impl.py b/tensorflow/python/ops/sets_impl.py index b0eecd8a1e..21e08d03d2 100644 --- a/tensorflow/python/ops/sets_impl.py +++ b/tensorflow/python/ops/sets_impl.py @@ -247,7 +247,7 @@ def set_difference(a, b, aminusb=True, validate_indices=True): # # collections.OrderedDict([ # ((0, 0, 0), 2), - # ((0, 0, 1), 3), + # ((0, 1, 0), 3), # ]) ``` -- GitLab From 8dc7a69b3bfc04872fde56fda595a7614ac643fe Mon Sep 17 00:00:00 2001 From: imsheridan Date: Tue, 20 Mar 2018 00:57:43 +0800 Subject: [PATCH 039/906] Fix the comments of tf.contrib.lookup.MutableHashTable insert operation --- tensorflow/contrib/lookup/lookup_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index 62f1c810fc..cc77cd5431 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -298,7 +298,7 @@ class MutableHashTable(LookupInterface): table = tf.contrib.lookup.MutableHashTable(key_dtype=tf.string, value_dtype=tf.int64, default_value=-1) - table.insert(keys, values) + sess.run(table.insert(keys, values)) out = table.lookup(query_keys) print(out.eval()) ``` @@ -494,7 +494,7 @@ class MutableDenseHashTable(LookupInterface): value_dtype=tf.int64, default_value=-1, empty_key=0) - table.insert(keys, values) + sess.run(table.insert(keys, values)) out = table.lookup(query_keys) print(out.eval()) ``` -- GitLab From b1cb65ab5218c13eb9d0f55b7f169cd676e032f3 Mon Sep 17 00:00:00 2001 From: Fanjin Zeng Date: Mon, 19 Mar 2018 10:29:45 -0700 Subject: [PATCH 040/906] Fix related doc clarification request on tf.contrib.lookup.MutableHashTable insert operation #17835 Make the doc example executable, and explicitly suggests that MutableDenseHashTable.insert is an operation rather than in-place computation. --- tensorflow/contrib/lookup/lookup_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index 62f1c810fc..c7a61fcac3 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -494,7 +494,7 @@ class MutableDenseHashTable(LookupInterface): value_dtype=tf.int64, default_value=-1, empty_key=0) - table.insert(keys, values) + sess.run(table.insert(keys, values)) out = table.lookup(query_keys) print(out.eval()) ``` -- GitLab From 8fded7872fe0921e0f90fac1891cda0c46a26855 Mon Sep 17 00:00:00 2001 From: jjsjann123 Date: Mon, 19 Mar 2018 16:35:29 -0700 Subject: [PATCH 041/906] [update TensorRT converter] (#17772) * [update TensorRT converter] fixed FusedBatchNorm to support broadcast; remove fp16 conversion for type int const add Snapshot in conversion (treated as identity) * [TensorRT converter batchnorm code cleaning] * TRT batchnorm code cleaning --- .../contrib/tensorrt/convert/convert_graph.cc | 1 + .../contrib/tensorrt/convert/convert_nodes.cc | 245 +++++++++--------- 2 files changed, 118 insertions(+), 128 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index eea8c8efa2..90447ee666 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -55,6 +55,7 @@ bool IsTensorRTCandidate(const tensorflow::NodeDef& node_def) { // Split it into a registration for each kernel. static const std::set candidate_ops = { "Identity", + "Snapshot", "Const", "Conv2D", "MaxPool", diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 4c00630cfe..7f4b57f9f4 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -346,11 +346,10 @@ void ReorderCKtoKC(const TRT_ShapedWeights& iweights, break; } case tensorflow::DataType::DT_HALF: { - Reorder2( - {k, c}, static_cast(iweights.GetValues()), - istrides, - static_cast(const_cast(oweights->GetValues())), - ostrides); + Reorder2({k, c}, static_cast(iweights.GetValues()), + istrides, static_cast( + const_cast(oweights->GetValues())), + ostrides); break; } default: @@ -998,9 +997,7 @@ enum class ConvolutionType { DEFAULT, DEPTHWISE_CONV }; tensorflow::Status ConvertConv2DHelper( Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, - std::vector* outputs, - int group // group ==0 specifies depthwise conv -) { + std::vector* outputs, int group) { const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); TFAttrs attrs(node_def); @@ -1134,9 +1131,9 @@ tensorflow::Status BinaryTensorOpTensor( CHECK_EQ_TYPE(tensor_r->getType(), dtype); auto op_pair = ops.find(node_def.op()); if (op_pair == ops.end()) - return tensorflow::errors::Unimplemented( - "binary op: " + node_def.op() + - " not supported at: " + node_def.name()); + return tensorflow::errors::Unimplemented("binary op: " + node_def.op() + + " not supported at: " + + node_def.name()); nvinfer1::IElementWiseLayer* layer = ctx.network()->addElementWise( *const_cast(tensor_l), @@ -1447,62 +1444,23 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - if (ctx.isFP16()) { - auto dtype_new = tensorflow::DataType::DT_HALF; - size_t len_data = tensorflow::DataTypeSize(dtype_new); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); - TTypes::Flat half_tensor = temp_tensor.flat(); - Eigen::DefaultDevice defd; - switch (dtype) { - case (tensorflow::DT_INT32): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_INT16): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_INT8): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_UINT8): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - default: - return tensorflow::errors::InvalidArgument( - "Datatype " + tensorflow::DataTypeString(dtype) + - " for FP16 conversion"); - break; - }; - memcpy(dst, half_tensor.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); - } else { - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); - len_data = std::max(len_data, len_tensor); - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.int_val().begin(), - weights_tensor.int_val() - .end()); // make a local copy first to flatten - // doesn't have to be contigous - memcpy(dst, tensor_data.data(), len_tensor); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } + // we should not have converted //if (ctx.isFP16()) { + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; + size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); + len_data = std::max(len_data, len_tensor); + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.int_val().begin(), + weights_tensor.int_val().end()); // make a local copy first to flatten + // doesn't have to be contigous + memcpy(dst, tensor_data.data(), len_tensor); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); } else if (!weights_tensor.tensor_content().empty()) { + // obsolete method. + // After optimization path, we do not see weights in this format. + // fp16 conversion technically should be needed here. VLOG(2) << "TENSOR!!!" << node_def.name(); const auto& content = weights_tensor.tensor_content(); @@ -1784,8 +1742,6 @@ tensorflow::Status ConvertConcat(Converter& ctx, TRT_ShapedWeights axis = inputs.at(input_size).weights(); TFAttrs attrs(node_def); - // auto attr_size = attrs.at("N")->i(); - // auto data_type = attrs.get("T"); auto index_type = attrs.get("Tidx"); // TODO(jie): handle data type @@ -1875,71 +1831,103 @@ tensorflow::Status ConvertFusedBatchNorm( "only is_training=false is supported, at " + node_def.name()); } nvinfer1::ITensor const* tensor = inputs.at(0).tensor(); - TRT_ShapedWeights scale_weights = inputs.at(1).weights(); - TRT_ShapedWeights offset_weights = inputs.at(2).weights(); - TRT_ShapedWeights mean_weights = inputs.at(3).weights(); - TRT_ShapedWeights variance_weights = inputs.at(4).weights(); - TRT_ShapedWeights dummy_power_weights(scale_weights.type_); - TRT_ShapedWeights combined_scale_weights = - ctx.get_temp_weights_like(scale_weights); - TRT_ShapedWeights combined_offset_weights = - ctx.get_temp_weights_like(offset_weights); - size_t nweight = scale_weights.count(); - if ((scale_weights.type_ == offset_weights.type_) && - (mean_weights.type_ == variance_weights.type_) && - (scale_weights.type_ == variance_weights.type_)) { - if ((scale_weights.type_ != tensorflow::DataType::DT_FLOAT) && - (scale_weights.type_ != tensorflow::DataType::DT_HALF)) { + + // Check parameter types + auto parameter_type = inputs.at(1).weights().type_; + if ((parameter_type != tensorflow::DataType::DT_FLOAT) && + (parameter_type != tensorflow::DataType::DT_HALF)) { + return tensorflow::errors::Unimplemented( + "only float32 or float16 weight data type is supported, for node " + + node_def.name() + " got " + tensorflow::DataTypeString(parameter_type)); + } + for (int i = 1; i < 5; i++) { + if (inputs.at(i).weights().type_ != parameter_type) { return tensorflow::errors::Unimplemented( - "only float32 or float16 weight data type is supported, for node " + - node_def.name() + " got " + - tensorflow::DataTypeString(scale_weights.type_)); + "Inconsistent parameter type for batchnormis not supported, at: " + + node_def.name()); } - if (scale_weights.type_ == tensorflow::DT_FLOAT) { - for (size_t i = 0; i < nweight; ++i) { - float scale = (static_cast(scale_weights.GetValues()))[i]; - float offset = - (static_cast(offset_weights.GetValues()))[i]; - float mean = (static_cast(mean_weights.GetValues()))[i]; - float variance = - (static_cast(variance_weights.GetValues()))[i]; - float& combined_scale_ref = const_cast( - static_cast(combined_scale_weights.GetValues()))[i]; - float& combined_offset_ref = const_cast( - static_cast(combined_offset_weights.GetValues()))[i]; - combined_scale_ref = scale / sqrtf(variance + epsilon); - combined_offset_ref = offset - mean * combined_scale_ref; - } - } else { - const Eigen::half* scale_vals = - (static_cast(scale_weights.GetValues())); - const Eigen::half* off_vals = - (static_cast(offset_weights.GetValues())); - const Eigen::half* mean_vals = - (static_cast(mean_weights.GetValues())); - const Eigen::half* variance_vals = - (static_cast(variance_weights.GetValues())); - Eigen::half* comb_scale_vals = const_cast( - static_cast(combined_scale_weights.GetValues())); - Eigen::half* comb_off_vals = const_cast( - static_cast(combined_offset_weights.GetValues())); - for (size_t i = 0; i < nweight; ++i) { - float scale(scale_vals[i]); - float offset(off_vals[i]); - float mean(mean_vals[i]); - float variance(variance_vals[i]); - float combined_scale_ref = scale / sqrtf(variance + epsilon); - comb_scale_vals[i] = Eigen::half(combined_scale_ref); - float combined_offset_ref = offset - mean * combined_scale_ref; - comb_off_vals[i] = Eigen::half(combined_offset_ref); + } + + TRT_ShapedWeights dummy_power_weights(parameter_type); + size_t nweight = 0; + for (int i = 1; i < 5; i++) { + nweight = std::max(nweight, (size_t)inputs.at(i).weights().count()); + } + TRT_ShapedWeights* ptr_shape_weights = nullptr; + for (int i = 1; i < 5; i++) { + if (inputs.at(i).weights().count() == nweight) { + ptr_shape_weights = + const_cast(&(inputs.at(i).weights())); + } else if (inputs.at(i).weights().count() != 1) { + return tensorflow::errors::InvalidArgument( + "Inconsistent batchnorm parameter count, at: " + node_def.name()); + } + } + // We could technically have two weights with different shape. + // that requires two addScale op, arguably less performant + TRT_ShapedWeights combined_scale_weights = + ctx.get_temp_weights_like(*ptr_shape_weights); + TRT_ShapedWeights combined_offset_weights = + ctx.get_temp_weights_like(*ptr_shape_weights); + + const Eigen::half* cast_vals_array[4]; + const float* vals_array[4]; + for (int j = 0; j < 4; j++) { + cast_vals_array[j] = + static_cast(inputs.at(j + 1).weights().GetValues()); + vals_array[j] = + static_cast(inputs.at(j + 1).weights().GetValues()); + } + Eigen::half* cast_combined_scale_vals = const_cast( + static_cast(combined_scale_weights.GetValues())); + Eigen::half* cast_combined_offset_vals = const_cast( + static_cast(combined_offset_weights.GetValues())); + float* combined_scale_vals = const_cast( + static_cast(combined_scale_weights.GetValues())); + float* combined_offset_vals = const_cast( + static_cast(combined_offset_weights.GetValues())); + + for (size_t i = 0; i < nweight; ++i) { + float batchnorm_data[4]; + for (int j = 0; j < 4; j++) { + if (inputs.at(j + 1).weights().count() != 1) { + if (parameter_type == tensorflow::DT_FLOAT) { + batchnorm_data[j] = vals_array[j][i]; + } else if (parameter_type == tensorflow::DT_HALF) { + batchnorm_data[j] = + Eigen::half_impl::half_to_float(cast_vals_array[j][i]); + } + } else { + if (parameter_type == tensorflow::DT_FLOAT) { + batchnorm_data[j] = vals_array[j][0]; + } else if (parameter_type == tensorflow::DT_HALF) { + batchnorm_data[j] = + Eigen::half_impl::half_to_float(cast_vals_array[j][0]); + } } } + float scale = batchnorm_data[0]; + float offset = batchnorm_data[1]; + float mean = batchnorm_data[2]; + float variance = batchnorm_data[3]; + float combined_scale_val = scale / sqrtf(variance + epsilon); + float combined_offset_val = offset - mean * combined_scale_val; + if (parameter_type == tensorflow::DT_FLOAT) { + combined_scale_vals[i] = combined_scale_val; + combined_offset_vals[i] = combined_offset_val; + } else if (parameter_type == tensorflow::DT_HALF) { + cast_combined_scale_vals[i] = Eigen::half(combined_scale_val); + cast_combined_offset_vals[i] = Eigen::half(combined_offset_val); + } } - nvinfer1::IScaleLayer* layer = ctx.network()->addScale( - *const_cast(tensor), nvinfer1::ScaleMode::kCHANNEL, - combined_offset_weights.GetWeightsForTRT(), - combined_scale_weights.GetWeightsForTRT(), - dummy_power_weights.GetWeightsForTRT()); + + nvinfer1::ScaleMode mode = nweight == 1 ? nvinfer1::ScaleMode::kUNIFORM + : nvinfer1::ScaleMode::kCHANNEL; + nvinfer1::IScaleLayer* layer = + ctx.network()->addScale(*const_cast(tensor), mode, + combined_offset_weights.GetWeightsForTRT(), + combined_scale_weights.GetWeightsForTRT(), + dummy_power_weights.GetWeightsForTRT()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); @@ -2050,6 +2038,7 @@ void Converter::register_op_converters() { op_registry_["Const"] = ConvertConst; // TODO(ben,jie): this is a temp hack. op_registry_["Identity"] = ConvertIdentity; // Identity should be removed + op_registry_["Snapshot"] = ConvertIdentity; // Snapshot should be removed // resnet_50_v1 slim implementation op_registry_["Add"] = ConvertBinary; -- GitLab From 98c955ee73e95591b00793f8fe9de5b1d588a0ea Mon Sep 17 00:00:00 2001 From: jjsjann123 Date: Tue, 20 Mar 2018 12:45:54 -0700 Subject: [PATCH 042/906] improve fp16 tftrt prediction (#17857) delay fp32 to fp16 conversion to reduce accumulated rounding error --- .../contrib/tensorrt/convert/convert_nodes.cc | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 7f4b57f9f4..979b5648c2 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -547,6 +547,19 @@ class Converter { } }; +TRT_ShapedWeights ConvertFP32ToFP16(Converter& ctx, + const TRT_ShapedWeights& weights_src) { + auto dtype_new = tensorflow::DataType::DT_HALF; + TRT_ShapedWeights weights = + ctx.get_temp_weights(dtype_new, weights_src.shape_); + const float* src = static_cast(weights_src.GetValues()); + Eigen::half* dst = const_cast( + static_cast(weights.GetValues())); + for (int64_t i = 0; i < weights_src.count(); i++) { + dst[i] = Eigen::half_impl::float_to_half_rtne(src[i]); + } + return weights; +} // **************************************************************************** // Constant folding functions // TODO(jie): once optimizer kicks in, we should have done constant folding @@ -956,6 +969,10 @@ tensorflow::Status BinaryTensorOpWeight( } } + if (ctx.isFP16()) { + weights = ConvertFP32ToFP16(ctx, weights); + } + // prepare weights TRT_ShapedWeights shift_weights(weights.type_); TRT_ShapedWeights scale_weights(weights.type_); @@ -1022,6 +1039,10 @@ tensorflow::Status ConvertConv2DHelper( VLOG(2) << "groups count: " << num_groups; TRT_ShapedWeights weights_rsck = inputs.at(1).weights(); + if (ctx.isFP16()) { + weights_rsck = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); + } + TRT_ShapedWeights weights = ctx.get_temp_weights_like(weights_rsck); ReorderRSCKToKCRS(weights_rsck, &weights, num_groups); TRT_ShapedWeights biases(weights.type_); @@ -1292,8 +1313,11 @@ tensorflow::Status ConvertScale(Converter& ctx, // Implement tensor binaryOp weight [channel wise] for now; const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); - // TODO(jie): handle NHWC/NCHW transpose; TRT_ShapedWeights weights = inputs.at(1).weights(); + if (ctx.isFP16()) { + weights = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); + } + TRT_ShapedWeights empty_weights(weights.type_); TFAttrs attrs(node_def); @@ -1388,33 +1412,16 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - if (ctx.isFP16()) { - auto dtype_new = tensorflow::DataType::DT_HALF; - size_t len_data = tensorflow::DataTypeSize(dtype_new); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); - auto half_tensor = temp_tensor.flat(); - Eigen::DefaultDevice defd; - half_tensor.device(defd) = - tensor.flat().template cast(); - memcpy(dst, half_tensor.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); - } else { - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.float_val().begin(), - weights_tensor.float_val() - .end()); // make a local copy first to flatten - memcpy(dst, tensor_data.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.float_val().begin(), + weights_tensor.float_val() + .end()); // make a local copy first to flatten + memcpy(dst, tensor_data.data(), len_data); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); } else if (!weights_tensor.int_val().empty()) { VLOG(2) << "int!!!" << node_def.name(); nvinfer1::Dims scalar_shape; -- GitLab From 8e4ee96bcce043bdb221e8fe116581ae90bae00d Mon Sep 17 00:00:00 2001 From: "Ziyue(Louis) Lu" Date: Tue, 20 Mar 2018 18:43:20 -0700 Subject: [PATCH 043/906] Updated README.md Edited the word 'lets you' to 'enables you to', which I think is better for this sentence. The word 'enable' here gives the readers a feeling that the architecture makes some awesome things possible, while 'let' is more like to give some permissions to the users. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cdb6e478d..0a309ebe2d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ **TensorFlow** is an open source software library for numerical computation using data flow graphs. The graph nodes represent mathematical operations, while the graph edges represent the multidimensional data arrays (tensors) that flow -between them. This flexible architecture lets you deploy computation to one +between them. This flexible architecture enables you to deploy computation to one or more CPUs or GPUs in a desktop, server, or mobile device without rewriting code. TensorFlow also includes TensorBoard, a data visualization toolkit. -- GitLab From e79eb0b8de130bf905a101608681e9c18561356c Mon Sep 17 00:00:00 2001 From: Amit Patankar Date: Tue, 20 Mar 2018 20:28:38 -0700 Subject: [PATCH 044/906] Fix windows GPU build scripts. (#17870) PiperOrigin-RevId: 188629017 --- tensorflow/tools/ci_build/windows/gpu/cmake/run_build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/windows/gpu/cmake/run_build.bat b/tensorflow/tools/ci_build/windows/gpu/cmake/run_build.bat index b87e4a9bec..4656afe025 100644 --- a/tensorflow/tools/ci_build/windows/gpu/cmake/run_build.bat +++ b/tensorflow/tools/ci_build/windows/gpu/cmake/run_build.bat @@ -37,7 +37,7 @@ SET CMAKE_DIR=%REPO_ROOT%\tensorflow\contrib\cmake SET MSBUILD_EXE="C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild.exe" :: Run cmake to create Visual Studio Project files. -%CMAKE_EXE% %CMAKE_DIR% -A x64 -DSWIG_EXECUTABLE=%SWIG_EXE% -DPYTHON_EXECUTABLE=%PY_EXE% -DCMAKE_BUILD_TYPE=Release -DPYTHON_LIBRARIES=%PY_LIB% -Dtensorflow_BUILD_PYTHON_TESTS=%BUILD_PYTHON_TESTS% -Dtensorflow_BUILD_CC_TESTS=%BUILD_CC_TESTS% -Dtensorflow_ENABLE_GPU=ON -DCUDNN_HOME=%CUDNN_HOME% -Dtensorflow_TF_NIGHTLY=%TF_NIGHTLY% -Dtensorflow_DISABLE_EIGEN_FORCEINLINE=%DISABLE_FORCEINLINE% -Dtensorflow_WIN_CPU_SIMD_OPTIONS=/arch:AVX +%CMAKE_EXE% %CMAKE_DIR% -A x64 -DSWIG_EXECUTABLE=%SWIG_EXE% -DPYTHON_EXECUTABLE=%PY_EXE% -DCMAKE_BUILD_TYPE=Release -DPYTHON_LIBRARIES=%PY_LIB% -Dtensorflow_BUILD_PYTHON_TESTS=%BUILD_PYTHON_TESTS% -Dtensorflow_BUILD_CC_TESTS=%BUILD_CC_TESTS% -Dtensorflow_ENABLE_GPU=ON -DCUDNN_HOME=%CUDNN_HOME% -Dtensorflow_TF_NIGHTLY=%TF_NIGHTLY% -Dtensorflow_DISABLE_EIGEN_FORCEINLINE=%DISABLE_FORCEINLINE% -Dtensorflow_WIN_CPU_SIMD_OPTIONS=/arch:AVX -G"Visual Studio 14" :: Run msbuild in the resulting VS project files to build a pip package. %MSBUILD_EXE% /p:Configuration=Release /maxcpucount:32 tf_python_build_pip_package.vcxproj -- GitLab From 7dd78367a19e101b45f0cafb5c4fbe6a3c840828 Mon Sep 17 00:00:00 2001 From: Sang Han Date: Wed, 21 Mar 2018 13:37:52 -0700 Subject: [PATCH 045/906] SetUsrMemDataHandle should return void --- tensorflow/core/util/mkl_util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index 34db96075d..9f58e40d94 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -1579,10 +1579,10 @@ class MklDnnData { } /// Set function for data buffer of user memory primitive. - inline void* SetUsrMemDataHandle(void* data_buffer) { + inline void SetUsrMemDataHandle(void* data_buffer) { CHECK_NOTNULL(user_memory_); CHECK_NOTNULL(data_buffer); - return user_memory_->set_data_handle(data_buffer); + user_memory_->set_data_handle(data_buffer); } /// Set function for data buffer of user memory primitive. -- GitLab From 4c56dcaade7cee9f75740e2f03e30bdf3a6f93be Mon Sep 17 00:00:00 2001 From: Yunxing Dai Date: Wed, 21 Mar 2018 14:12:07 -0700 Subject: [PATCH 046/906] [XLA][BF16] Add bf16 rounding function. We now use truncation to convert a F32 to BF16 by default. This CL adds a rounding method (basically a roll forward of the rounding part in cl/175252067). PiperOrigin-RevId: 189965138 --- tensorflow/core/framework/bfloat16_test.cc | 39 +++-- tensorflow/core/lib/bfloat16/bfloat16.h | 188 ++++++++++++++++++++- 2 files changed, 216 insertions(+), 11 deletions(-) diff --git a/tensorflow/core/framework/bfloat16_test.cc b/tensorflow/core/framework/bfloat16_test.cc index 17e6209f8e..206396a25a 100644 --- a/tensorflow/core/framework/bfloat16_test.cc +++ b/tensorflow/core/framework/bfloat16_test.cc @@ -37,19 +37,27 @@ float BinaryToFloat(uint32_t sign, uint32_t exponent, uint32_t high_mantissa, struct Bfloat16TestParam { float input; - float expected; + float expected_truncation; + float expected_rounding; }; class Bfloat16Test : public ::testing::Test, public ::testing::WithParamInterface {}; TEST_P(Bfloat16Test, TruncateTest) { - bfloat16 a(GetParam().input); + bfloat16 truncated(GetParam().input); if (std::isnan(GetParam().input)) { - EXPECT_TRUE(std::isnan(float(a)) || std::isinf(float(a))); + EXPECT_TRUE(std::isnan(float(truncated)) || std::isinf(float(truncated))); return; } - EXPECT_EQ(GetParam().expected, float(a)); + EXPECT_EQ(GetParam().expected_truncation, float(truncated)); + + bfloat16 rounded = bfloat16::round_to_bfloat16((GetParam().input)); + if (std::isnan(GetParam().input)) { + EXPECT_TRUE(std::isnan(float(rounded)) || std::isinf(float(rounded))); + return; + } + EXPECT_EQ(GetParam().expected_rounding, float(rounded)); } INSTANTIATE_TEST_CASE_P( @@ -57,37 +65,48 @@ INSTANTIATE_TEST_CASE_P( ::testing::Values( Bfloat16TestParam{ BinaryToFloat(0, 0b10000000, 0b1001000, 0b1111010111000011), - BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000)}, + BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), + BinaryToFloat(0, 0b10000000, 0b1001001, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(1, 0b10000000, 0b1001000, 0b1111010111000011), - BinaryToFloat(1, 0b10000000, 0b1001000, 0b0000000000000000)}, + BinaryToFloat(1, 0b10000000, 0b1001000, 0b0000000000000000), + BinaryToFloat(1, 0b10000000, 0b1001001, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b10000000, 0b1001000, 0b1000000000000000), + BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b11111111, 0b0000000, 0b0000000000000001), - BinaryToFloat(0, 0b11111111, 0b0000000, 0b0000000000000000)}, + BinaryToFloat(0, 0b11111111, 0b0000000, 0b0000000000000000), + BinaryToFloat(0, 0b11111111, 0b1000000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b11111111, 0b1111111, 0b1111111111111111), - BinaryToFloat(0, 0b11111111, 0b1111111, 0b0000000000000000)}, + BinaryToFloat(0, 0b11111111, 0b1111111, 0b0000000000000000), + BinaryToFloat(0, 0b11111111, 0b1000000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(1, 0b10000000, 0b1001000, 0b1100000000000000), - BinaryToFloat(1, 0b10000000, 0b1001000, 0b0000000000000000)}, + BinaryToFloat(1, 0b10000000, 0b1001000, 0b0000000000000000), + BinaryToFloat(1, 0b10000000, 0b1001001, 0b0000000000000000)}, Bfloat16TestParam{ + BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b10000000, 0b1001000, 0b0100000000000000), + BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b10000000, 0b1001000, 0b1000000000000000), + BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b10000000, 0b1001000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b00000000, 0b1001000, 0b1000000000000000), + BinaryToFloat(0, 0b00000000, 0b1001000, 0b0000000000000000), BinaryToFloat(0, 0b00000000, 0b1001000, 0b0000000000000000)}, Bfloat16TestParam{ BinaryToFloat(0, 0b00000000, 0b1111111, 0b1100000000000000), - BinaryToFloat(0, 0b00000000, 0b1111111, 0b0000000000000000)})); + BinaryToFloat(0, 0b00000000, 0b1111111, 0b0000000000000000), + BinaryToFloat(0, 0b00000001, 0b0000000, 0b0000000000000000)})); TEST(Bfloat16Test, Conversion) { float a[100]; diff --git a/tensorflow/core/lib/bfloat16/bfloat16.h b/tensorflow/core/lib/bfloat16/bfloat16.h index 075a8d1430..126e5a17af 100644 --- a/tensorflow/core/lib/bfloat16/bfloat16.h +++ b/tensorflow/core/lib/bfloat16/bfloat16.h @@ -165,6 +165,192 @@ struct bfloat16 { return complex128(double(*this), double(0.0)); } + union FP32 { + unsigned int u; + float f; + }; + + // Converts a float point to bfloat16, with round-nearest-to-even as rounding + // method. + // TODO(b/69266521): Add a truncate_to_bfloat16 function and make this + // function as default behavior. + // TODO: There is a slightly faster implementation (8% faster on CPU) + // than this (documented in cl/175987786), that is exponentially harder to + // understand and document. Switch to the faster version when converting to + // BF16 becomes compute-bound. + B16_DEVICE_FUNC static bfloat16 round_to_bfloat16(float v) { + uint32_t input; + FP32 f; + f.f = v; + input = f.u; + bfloat16 output; + + if (float_isnan(v)) { + // If the value is a NaN, squash it to a qNaN with msb of fraction set, + // this makes sure after truncation we don't end up with an inf. + // + // qNaN magic: All exponent bits set + most significant bit of fraction + // set. + output.value = 0x7fc0; + } else { + // Fast rounding algorithm that rounds a half value to nearest even. This + // reduces expected error when we convert a large number of floats. Here + // is how it works: + // + // Definitions: + // To convert a float 32 to bfloat16, a float 32 can be viewed as 32 bits + // with the following tags: + // + // Sign | Exp (8 bits) | Frac (23 bits) + // S EEEEEEEE FFFFFFLRTTTTTTTTTTTTTTT + // + // S: Sign bit. + // E: Exponent bits. + // F: First 6 bits of fraction. + // L: Least significant bit of resulting bfloat16 if we truncate away the + // rest of the float32. This is also the 7th bit of fraction + // R: Rounding bit, 8th bit of fraction. + // T: Sticky bits, rest of fraction, 15 bits. + // + // To round half to nearest even, there are 3 cases where we want to round + // down (simply truncate the result of the bits away, which consists of + // rounding bit and sticky bits) and two cases where we want to round up + // (truncate then add one to the result). + // + // The fast converting algorithm simply adds lsb (L) to 0x7fff (15 bits of + // 1s) as the rounding bias, adds the rounding bias to the input, then + // truncates the last 16 bits away. + // + // To understand how it works, we can analyze this algorithm case by case: + // + // 1. L = 0, R = 0: + // Expect: round down, this is less than half value. + // + // Algorithm: + // - Rounding bias: 0x7fff + 0 = 0x7fff + // - Adding rounding bias to input may create any carry, depending on + // whether there is any value set to 1 in T bits. + // - R may be set to 1 if there is a carry. + // - L remains 0. + // - Note that this case also handles Inf and -Inf, where all fraction + // bits, including L, R and Ts are all 0. The output remains Inf after + // this algorithm. + // + // 2. L = 1, R = 0: + // Expect: round down, this is less than half value. + // + // Algorithm: + // - Rounding bias: 0x7fff + 1 = 0x8000 + // - Adding rounding bias to input doesn't change sticky bits but + // adds 1 to rounding bit. + // - L remains 1. + // + // 3. L = 0, R = 1, all of T are 0: + // Expect: round down, this is exactly at half, the result is already + // even (L=0). + // + // Algorithm: + // - Rounding bias: 0x7fff + 0 = 0x7fff + // - Adding rounding bias to input sets all sticky bits to 1, but + // doesn't create a carry. + // - R remains 1. + // - L remains 0. + // + // 4. L = 1, R = 1: + // Expect: round up, this is exactly at half, the result needs to be + // round to the next even number. + // + // Algorithm: + // - Rounding bias: 0x7fff + 1 = 0x8000 + // - Adding rounding bias to input doesn't change sticky bits, but + // creates a carry from rounding bit. + // - The carry sets L to 0, creates another carry bit and propagate + // forward to F bits. + // - If all the F bits are 1, a carry then propagates to the exponent + // bits, which then creates the minimum value with the next exponent + // value. Note that we won't have the case where exponents are all 1, + // since that's either a NaN (handled in the other if condition) or inf + // (handled in case 1). + // + // 5. L = 0, R = 1, any of T is 1: + // Expect: round up, this is greater than half. + // + // Algorithm: + // - Rounding bias: 0x7fff + 0 = 0x7fff + // - Adding rounding bias to input creates a carry from sticky bits, + // sets rounding bit to 0, then create another carry. + // - The second carry sets L to 1. + // + // Examples: + // + // Exact half value that is already even: + // Input: + // Sign | Exp (8 bit) | Frac (first 7 bit) | Frac (last 16 bit) + // S E E E E E E E E F F F F F F L RTTTTTTTTTTTTTTT + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1000000000000000 + // + // This falls into case 3. We truncate the rest of 16 bits and no + // carry is created into F and L: + // + // Output: + // Sign | Exp (8 bit) | Frac (first 7 bit) + // S E E E E E E E E F F F F F F L + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 + // + // Exact half value, round to next even number: + // Input: + // Sign | Exp (8 bit) | Frac (first 7 bit) | Frac (last 16 bit) + // S E E E E E E E E F F F F F F L RTTTTTTTTTTTTTTT + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1000000000000000 + // + // This falls into case 4. We create a carry from R and T, + // which then propagates into L and F: + // + // Output: + // Sign | Exp (8 bit) | Frac (first 7 bit) + // S E E E E E E E E F F F F F F L + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 + // + // + // Max denormal value round to min normal value: + // Input: + // Sign | Exp (8 bit) | Frac (first 7 bit) | Frac (last 16 bit) + // S E E E E E E E E F F F F F F L RTTTTTTTTTTTTTTT + // 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1111111111111111 + // + // This falls into case 4. We create a carry from R and T, + // propagate into L and F, which then propagates into exponent + // bits: + // + // Output: + // Sign | Exp (8 bit) | Frac (first 7 bit) + // S E E E E E E E E F F F F F F L + // 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 + // + // Max normal value round to Inf: + // Input: + // Sign | Exp (8 bit) | Frac (first 7 bit) | Frac (last 16 bit) + // S E E E E E E E E F F F F F F L RTTTTTTTTTTTTTTT + // 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1111111111111111 + // + // This falls into case 4. We create a carry from R and T, + // propagate into L and F, which then propagates into exponent + // bits: + // + // Sign | Exp (8 bit) | Frac (first 7 bit) + // S E E E E E E E E F F F F F F L + // 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 + // + // + // Least significant bit of resulting bfloat. + uint32_t lsb = (input >> 16) & 1; + uint32_t rounding_bias = 0x7fff + lsb; + input += rounding_bias; + output.value = static_cast(input >> 16); + } + return output; + } + static bfloat16 epsilon() { bfloat16 x; x.value = 0x3c00; // 0x1.0p-7 @@ -177,7 +363,7 @@ struct bfloat16 { static const uint16_t NAN_VALUE = 0x7FC0; private: - B16_DEVICE_FUNC bool float_isnan(const float& x) { + B16_DEVICE_FUNC static bool float_isnan(const float& x) { #ifdef __CUDA_ARCH__ return ::isnan(x); #else -- GitLab From c2b346538f8a651bd8adb5fa557bdfac0394c2c3 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Wed, 21 Mar 2018 14:26:14 -0700 Subject: [PATCH 047/906] Avoid taking a reference on a temporary value PiperOrigin-RevId: 189967517 --- tensorflow/core/grappler/optimizers/loop_optimizer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer.cc b/tensorflow/core/grappler/optimizers/loop_optimizer.cc index bd0d94b83f..a063dc3381 100644 --- a/tensorflow/core/grappler/optimizers/loop_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/loop_optimizer.cc @@ -368,7 +368,7 @@ Status LoopOptimizer::FindInvariantNodes(NodeDef* node) { bool is_invariant = true; for (const auto& input : consumer->input()) { if (!IsControlInput(input)) { - const auto& name = NodeName(input); + const string name = NodeName(input); auto* producer = node_map_->GetNode(name); if (!invariant_nodes_.count(producer)) { if (IsConstant(*producer)) { -- GitLab From a07bd80e27dd41a1b6a3f4c2e1954ae573453cda Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 21 Mar 2018 14:59:06 -0700 Subject: [PATCH 048/906] Add an alternative "no_contrib" BUILD target to tensorflow/python to avoid including contrib packages PiperOrigin-RevId: 189973359 --- tensorflow/python/BUILD | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 54e944c264..079905781d 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -58,6 +58,18 @@ py_library( "//tensorflow/tools/api/generator:__pkg__", "//tensorflow/tools/quantization:__pkg__", # TODO(b/34059704): remove when fixed ], + deps = [":no_contrib"] + if_not_windows([ + "//tensorflow/contrib:contrib_py", + ]), +) + +py_library( + name = "no_contrib", + srcs = ["__init__.py"], + srcs_version = "PY2AND3", + visibility = [ + "//tensorflow:__pkg__", + ], deps = [ ":array_ops", ":bitwise_ops", @@ -86,6 +98,7 @@ py_library( ":ops", ":platform", ":pywrap_tensorflow", + ":saver_test_utils", ":script_ops", ":session_ops", ":sets", @@ -95,31 +108,28 @@ py_library( ":standard_ops", ":state_ops", ":string_ops", + ":subscribe", ":summary", ":tensor_array_ops", - ":training", - ":saver_test_utils", - ":subscribe", ":test_ops", # TODO: Break testing code out into separate rule. - ":tf_item", ":tf_cluster", + ":tf_item", ":tf_optimizer", + ":training", ":util", ":weights_broadcast_ops", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python/data", "//tensorflow/python/estimator:estimator_py", "//tensorflow/python/feature_column:feature_column_py", "//tensorflow/python/keras", - "//tensorflow/python/ops/losses", "//tensorflow/python/ops/distributions", "//tensorflow/python/ops/linalg", + "//tensorflow/python/ops/losses", "//tensorflow/python/profiler", "//tensorflow/python/saved_model", - ] + if_not_windows([ - "//tensorflow/contrib:contrib_py", - ]), + "//third_party/py/numpy", + ], ) tf_py_build_info_genrule() -- GitLab From 9cd65e9a9081640934b2b78cf84b6e51ddd69796 Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Wed, 21 Mar 2018 15:23:07 -0700 Subject: [PATCH 049/906] [TF:XLA] do not emit bfloat16 sum reductions from tf2xla bfloat16 is a storage format, not a computation format. Doing reductions in this reduced precision is prone to quickly overflow. Instead, emit a float32 computation, and wrap the reduce params and result in conversions to and from float32. PiperOrigin-RevId: 189977590 --- .../compiler/tf2xla/kernels/batch_norm_op.cc | 57 +++++------ .../compiler/tf2xla/kernels/bias_ops.cc | 13 ++- .../compiler/tf2xla/kernels/conv_ops.cc | 6 +- .../tf2xla/kernels/fake_quantize_ops.cc | 18 +++- .../compiler/tf2xla/kernels/image_ops.cc | 8 +- .../compiler/tf2xla/kernels/l2loss_op.cc | 23 ++--- tensorflow/compiler/tf2xla/kernels/lrn_ops.cc | 36 ++++--- .../compiler/tf2xla/kernels/pooling_ops.cc | 95 ++++++++++--------- .../compiler/tf2xla/kernels/reduction_ops.cc | 42 +++++--- .../compiler/tf2xla/kernels/reduction_ops.h | 9 +- .../tf2xla/kernels/reduction_ops_common.cc | 46 ++++----- .../compiler/tf2xla/kernels/scan_ops.cc | 8 +- .../compiler/tf2xla/kernels/softmax_op.cc | 51 +++++----- tensorflow/compiler/tf2xla/xla_helpers.cc | 17 ++++ tensorflow/compiler/tf2xla/xla_helpers.h | 12 +++ tensorflow/compiler/xla/literal_util.cc | 3 + tensorflow/compiler/xla/tests/convert_test.cc | 40 ++++++++ 17 files changed, 305 insertions(+), 179 deletions(-) diff --git a/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc b/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc index a249b1869f..931175be11 100644 --- a/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc @@ -118,30 +118,24 @@ class FusedBatchNormGradOp : public XlaOpKernel { } void Compile(XlaOpKernelContext* ctx) override { - xla::ComputationBuilder* b = ctx->builder(); - - auto grad_backprop = ctx->Input(0); - auto activations = ctx->Input(1); - auto scale = ctx->Input(2); - auto mean = ctx->Input(3); - auto var = ctx->Input(4); - - TensorShape input_shape = ctx->InputShape(0); - int feature_index = - GetTensorFeatureDimIndex(input_shape.dims(), data_format_); - + xla::ComputationBuilder* const b = ctx->builder(); DataType input_dtype = ctx->input_type(0); DataType scale_dtype = ctx->input_type(2); - xla::PrimitiveType input_type; - OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(input_dtype, &input_type)); - xla::PrimitiveType scale_type; - OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(scale_dtype, &scale_type)); // TODO(b/69928690): support mixed precision in the XLA batch normalization // operators. For now, cast everything to the statistics type (which // may be more precise than the input type). - grad_backprop = b->ConvertElementType(grad_backprop, scale_type); - activations = b->ConvertElementType(activations, scale_type); + auto grad_backprop = + XlaHelpers::ConvertElementType(b, ctx->Input(0), scale_dtype); + auto activations = + XlaHelpers::ConvertElementType(b, ctx->Input(1), scale_dtype); + auto scale = ctx->Input(2); + auto mean = ctx->Input(3); + auto var = ctx->Input(4); + + const int input_dims = ctx->InputShape(0).dims(); + const int feature_index = + GetTensorFeatureDimIndex(input_dims, data_format_); xla::ComputationDataHandle x_backprop; xla::ComputationDataHandle scale_backprop; @@ -156,7 +150,7 @@ class FusedBatchNormGradOp : public XlaOpKernel { offset_backprop = b->GetTupleElement(output, 2); } else { // Reduce over all dimensions except the feature dim. - std::vector reduction_dims(input_shape.dims() - 1); + std::vector reduction_dims(input_dims - 1); std::iota(reduction_dims.begin(), reduction_dims.begin() + feature_index, 0); std::iota(reduction_dims.begin() + feature_index, reduction_dims.end(), @@ -165,9 +159,14 @@ class FusedBatchNormGradOp : public XlaOpKernel { // scale_backprop = y_backprop * ((x - pop_mean) * rsqrt(pop_var + // epsilon)) // x_backprop = y_backprop * (scale * rsqrt(pop_var + epsilon)) - offset_backprop = - b->Reduce(grad_backprop, XlaHelpers::Zero(b, scale_dtype), - *ctx->GetOrCreateAdd(scale_dtype), reduction_dims); + const DataType accumulation_type = + XlaHelpers::SumAccumulationType(scale_dtype); + auto converted = + XlaHelpers::ConvertElementType(b, grad_backprop, accumulation_type); + auto reduce = + b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), reduction_dims); + offset_backprop = XlaHelpers::ConvertElementType(b, reduce, scale_dtype); // scratch1 = rsqrt(pop_var + epsilon) auto neg_half = XlaHelpers::FloatLiteral(b, scale_dtype, -0.5); @@ -175,17 +174,21 @@ class FusedBatchNormGradOp : public XlaOpKernel { b->Pow(b->Add(var, b->ConstantR0(epsilon_)), neg_half); // scratch2 = sum(y_backprop * (x - mean)) - auto scratch2 = b->Reduce( - b->Mul(grad_backprop, b->Sub(activations, mean, {feature_index})), - XlaHelpers::Zero(b, scale_dtype), *ctx->GetOrCreateAdd(scale_dtype), - reduction_dims); + auto mul = + b->Mul(grad_backprop, b->Sub(activations, mean, {feature_index})); + converted = XlaHelpers::ConvertElementType(b, mul, accumulation_type); + reduce = + b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), reduction_dims); + auto scratch2 = XlaHelpers::ConvertElementType(b, reduce, scale_dtype); x_backprop = b->Mul(grad_backprop, b->Mul(scratch1, scale), {feature_index}); scale_backprop = b->Mul(scratch1, scratch2); } - ctx->SetOutput(0, b->ConvertElementType(x_backprop, input_type)); + ctx->SetOutput(0, + XlaHelpers::ConvertElementType(b, x_backprop, input_dtype)); ctx->SetOutput(1, scale_backprop); ctx->SetOutput(2, offset_backprop); ctx->SetConstantOutput(3, Tensor(scale_dtype, {})); diff --git a/tensorflow/compiler/tf2xla/kernels/bias_ops.cc b/tensorflow/compiler/tf2xla/kernels/bias_ops.cc index c667b4e3e3..ed33b8ed2e 100644 --- a/tensorflow/compiler/tf2xla/kernels/bias_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/bias_ops.cc @@ -103,10 +103,15 @@ class BiasAddGradOp : public XlaOpKernel { std::iota(reduce_dims.begin(), reduce_dims.begin() + feature_dim, 0); std::iota(reduce_dims.begin() + feature_dim, reduce_dims.end(), feature_dim + 1); - xla::ComputationDataHandle result = ctx->builder()->Reduce( - ctx->Input(0), XlaHelpers::Zero(ctx->builder(), input_type(0)), - *ctx->GetOrCreateAdd(input_type(0)), reduce_dims); - ctx->SetOutput(0, result); + xla::ComputationBuilder* const b = ctx->builder(); + const DataType accumulation_type = + XlaHelpers::SumAccumulationType(input_type(0)); + auto converted = + XlaHelpers::ConvertElementType(b, ctx->Input(0), accumulation_type); + auto reduce = + b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), reduce_dims); + ctx->SetOutput(0, XlaHelpers::ConvertElementType(b, reduce, input_type(0))); } private: diff --git a/tensorflow/compiler/tf2xla/kernels/conv_ops.cc b/tensorflow/compiler/tf2xla/kernels/conv_ops.cc index 81cea6d376..c0ee0c9c2e 100644 --- a/tensorflow/compiler/tf2xla/kernels/conv_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/conv_ops.cc @@ -58,7 +58,7 @@ xla::ComputationDataHandle CreateExpandedZero( // Create a mask for depthwise convolution that will make a normal convolution // produce the same results as a depthwise convolution. For a [2, 2, 3, 2] -// depthwise filter this returns a [2, 2, 3, 6] tesnsor +// depthwise filter this returns a [2, 2, 3, 6] tensor // 1 1 0 0 0 0 1 1 0 0 0 0 // 0 0 1 1 0 0 0 0 1 1 0 0 // 0 0 0 0 1 1 0 0 0 0 1 1 @@ -166,6 +166,10 @@ xla::ComputationDataHandle ContractFilterForDepthwiseBackprop( CreateExpandedFilterMask(filter_shape, builder), filter_backprop, CreateExpandedZero(filter_shape, dtype, builder)); return builder->Reshape( + // This reduce does not need inputs to be converted with + // XlaHelpers::SumAccumulationType() since the ExpandedFilterMask with + // ExpandedZero guarantees that only one element is non zero, so there + // cannot be accumulated precision error. builder->Reduce(masked_expanded_filter, XlaHelpers::Zero(builder, dtype), *ctx->GetOrCreateAdd(dtype), {expanded_filter_shape.dims() - 2}), diff --git a/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc b/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc index 453a32c494..99470d70e7 100644 --- a/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc @@ -247,6 +247,8 @@ class FakeQuantWithMinMaxVarsGradOp : public XlaOpKernel { const TensorShape gradient_shape = ctx->InputShape(0); xla::ComputationDataHandle input = ctx->Input(1); const DataType data_type = ctx->input_type(1); + const DataType accumulation_type = + XlaHelpers::SumAccumulationType(data_type); xla::ComputationDataHandle input_min = ctx->Input(2); xla::ComputationDataHandle input_max = ctx->Input(3); @@ -265,15 +267,23 @@ class FakeQuantWithMinMaxVarsGradOp : public XlaOpKernel { ctx->SetOutput(0, output0); xla::ComputationDataHandle below_min = b->Lt(input, nudged_input_min); + xla::ComputationDataHandle select1 = b->Select(below_min, gradient, zeroes); + xla::ComputationDataHandle reduce1 = b->ReduceAll( + XlaHelpers::ConvertElementType(b, select1, accumulation_type), + XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type)); xla::ComputationDataHandle output1 = - b->ReduceAll(b->Select(below_min, gradient, zeroes), zero, - *ctx->GetOrCreateAdd(data_type)); + XlaHelpers::ConvertElementType(b, reduce1, data_type); ctx->SetOutput(1, output1); xla::ComputationDataHandle above_max = b->Gt(input, nudged_input_max); + xla::ComputationDataHandle select2 = b->Select(above_max, gradient, zeroes); + xla::ComputationDataHandle reduce2 = b->ReduceAll( + XlaHelpers::ConvertElementType(b, select2, accumulation_type), + XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type)); xla::ComputationDataHandle output2 = - b->ReduceAll(b->Select(above_max, gradient, zeroes), zero, - *ctx->GetOrCreateAdd(data_type)); + XlaHelpers::ConvertElementType(b, reduce2, data_type); ctx->SetOutput(2, output2); } diff --git a/tensorflow/compiler/tf2xla/kernels/image_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_ops.cc index f22f384256..5eeda79a93 100644 --- a/tensorflow/compiler/tf2xla/kernels/image_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/image_ops.cc @@ -180,9 +180,13 @@ class AdjustContrastOpV2 : public XlaOpKernel { DataType type = context->input_type(0); - auto output = b->Reduce(input, /*init_value=*/XlaHelpers::Zero(b, type), - /*computation=*/*context->GetOrCreateAdd(type), + const DataType accumulation_type = XlaHelpers::SumAccumulationType(type); + auto converted = + XlaHelpers::ConvertElementType(b, input, accumulation_type); + auto reduce = b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *context->GetOrCreateAdd(accumulation_type), {height_dim, width_dim}); + auto output = XlaHelpers::ConvertElementType(b, reduce, type); output = b->Div(output, XlaHelpers::FloatLiteral(b, type, height * width)); std::vector broadcast_dims(input_shape.dims() - 2); diff --git a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc index d096415087..c177f08d9c 100644 --- a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc @@ -29,21 +29,22 @@ class L2LossOp : public XlaOpKernel { explicit L2LossOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {} void Compile(XlaOpKernelContext* ctx) override { - const TensorShape input_shape = ctx->InputShape(0); + std::vector dims(ctx->InputShape(0).dims()); + std::iota(dims.begin(), dims.end(), 0); DataType dtype = ctx->input_type(0); - xla::ComputationBuilder* b = ctx->builder(); - - auto zero = XlaHelpers::Zero(b, dtype); - auto two = XlaHelpers::IntegerLiteral(b, dtype, 2); - const xla::Computation& add = *ctx->GetOrCreateAdd(dtype); - - std::vector dims(input_shape.dims()); - std::iota(dims.begin(), dims.end(), 0); + xla::ComputationBuilder* const b = ctx->builder(); // output = sum(t ** 2) / 2 - auto x = ctx->Input(0); - ctx->SetOutput(0, b->Div(b->Reduce(b->Mul(x, x), zero, add, dims), two)); + const DataType accumulation_type = XlaHelpers::SumAccumulationType(dtype); + auto t = + XlaHelpers::ConvertElementType(b, ctx->Input(0), accumulation_type); + auto square = b->Mul(t, t); + auto reduce = b->Reduce(square, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), dims); + auto deconverted = XlaHelpers::ConvertElementType(b, reduce, dtype); + auto two = XlaHelpers::IntegerLiteral(b, dtype, 2); + ctx->SetOutput(0, b->Div(deconverted, two)); } }; diff --git a/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc b/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc index 759d1a1a2d..1cfee3070f 100644 --- a/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc @@ -47,12 +47,17 @@ class LRNOp : public XlaOpKernel { // We use a window of depth_radius_ * 2 + 1, to account for the current // element and a depth_radius_ on either side. - auto squared = builder->Mul(input, input); - auto sqr_sum = builder->ReduceWindow( - squared, XlaHelpers::Zero(builder, input_type(0)), - *ctx->GetOrCreateAdd(input_type(0)), + auto accumulation_type = XlaHelpers::SumAccumulationType(input_type(0)); + auto converted = + XlaHelpers::ConvertElementType(builder, input, accumulation_type); + auto squared = builder->Mul(converted, converted); + auto reduce = builder->ReduceWindow( + squared, XlaHelpers::Zero(builder, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), /* window_dimensions = */ {1, 1, 1, depth_radius_ * 2 + 1}, /* window_strides = */ {1, 1, 1, 1}, xla::Padding::kSame); + auto sqr_sum = + XlaHelpers::ConvertElementType(builder, reduce, input_type(0)); auto scale = builder->Pow( builder->Add(builder->ConstantR0(bias_), @@ -130,12 +135,17 @@ class LRNGradOp : public XlaOpKernel { // dyi *= out_grads[j] // grads[k] += dyi - auto squared = builder->Mul(in_image, in_image); - auto sqr_sum = builder->ReduceWindow( - squared, XlaHelpers::Zero(builder, input_type(0)), - *ctx->GetOrCreateAdd(input_type(0)), + auto accumulation_type = XlaHelpers::SumAccumulationType(input_type(0)); + auto converted = + XlaHelpers::ConvertElementType(builder, in_image, accumulation_type); + auto squared = builder->Mul(converted, converted); + auto reduce = builder->ReduceWindow( + squared, XlaHelpers::Zero(builder, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), /* window_dimensions = */ {1, 1, 1, depth_radius_ * 2 + 1}, /* window_strides = */ {1, 1, 1, 1}, xla::Padding::kSame); + auto sqr_sum = + XlaHelpers::ConvertElementType(builder, reduce, input_type(0)); auto norm = builder->Add(builder->ConstantR0(bias_), @@ -146,11 +156,15 @@ class LRNGradOp : public XlaOpKernel { builder->Div(out_image, norm)), in_grads); - auto dy_reduced = builder->ReduceWindow( - dy, XlaHelpers::Zero(builder, input_type(0)), - *ctx->GetOrCreateAdd(input_type(0)), + auto converted_dy = + XlaHelpers::ConvertElementType(builder, dy, accumulation_type); + auto dy_reduce = builder->ReduceWindow( + converted_dy, XlaHelpers::Zero(builder, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), /* window_dimensions = */ {1, 1, 1, depth_radius_ * 2 + 1}, /* window_strides = */ {1, 1, 1, 1}, xla::Padding::kSame); + auto dy_reduced = + XlaHelpers::ConvertElementType(builder, dy_reduce, input_type(0)); xla::ComputationDataHandle gradients = builder->Add( builder->Mul(in_image, dy_reduced), diff --git a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc index 086a9491aa..5f635dd1bc 100644 --- a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc @@ -35,8 +35,11 @@ namespace { // Superclass of pooling ops. class PoolingOp : public XlaOpKernel { public: - PoolingOp(OpKernelConstruction* ctx, int num_spatial_dims) - : XlaOpKernel(ctx), num_spatial_dims_(num_spatial_dims) { + PoolingOp(OpKernelConstruction* ctx, int num_spatial_dims, + const DataType reduction_type) + : XlaOpKernel(ctx), + num_spatial_dims_(num_spatial_dims), + reduction_type_(reduction_type) { if (ctx->num_inputs() == 1) { std::vector ksize_int; std::vector stride_int; @@ -63,12 +66,10 @@ class PoolingOp : public XlaOpKernel { int num_dims() const { return num_spatial_dims_ + 2; } // Method that builds an initial value to use in reductions. - virtual xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b, - DataType data_type) = 0; + virtual xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b) = 0; // The reduction operation to apply to each window. - virtual const xla::Computation* Reduction(XlaOpKernelContext* ctx, - DataType dtype) = 0; + virtual const xla::Computation* Reduction(XlaOpKernelContext* ctx) = 0; // A post-processing operation to apply on the outputs of the ReduceWindow. virtual xla::ComputationDataHandle PostProcessOutput( @@ -76,9 +77,6 @@ class PoolingOp : public XlaOpKernel { DataType dtype, const TensorShape& input_shape) = 0; void Compile(XlaOpKernelContext* ctx) override { - xla::ComputationDataHandle input = ctx->Input(0); - const TensorShape input_shape = ctx->InputShape(0); - std::vector ksize = ksize_; std::vector stride = stride_; if (ctx->num_inputs() != 1) { @@ -106,16 +104,20 @@ class PoolingOp : public XlaOpKernel { stride.clear(); OP_REQUIRES_OK(ctx, ctx->ConstantInputAsIntVector(2, &stride)); } + const TensorShape input_shape = ctx->InputShape(0); OP_REQUIRES(ctx, input_shape.dims() == num_dims(), errors::InvalidArgument("Input to ", type_string(), " operator must have ", num_dims(), " dimensions")); - const DataType type = input_type(0); - xla::ComputationDataHandle pooled = ctx->builder()->ReduceWindow( - input, InitValue(ctx->builder(), type), *Reduction(ctx, type), ksize, - stride, padding_); - ctx->SetOutput(0, PostProcessOutput(ctx, pooled, type, input_shape)); + xla::ComputationBuilder* const b = ctx->builder(); + auto input = + XlaHelpers::ConvertElementType(b, ctx->Input(0), reduction_type_); + auto reduce = ctx->builder()->ReduceWindow( + input, InitValue(b), *Reduction(ctx), ksize, stride, padding_); + auto pooled = XlaHelpers::ConvertElementType(b, reduce, input_type(0)); + ctx->SetOutput(0, + PostProcessOutput(ctx, pooled, input_type(0), input_shape)); } protected: @@ -124,21 +126,21 @@ class PoolingOp : public XlaOpKernel { std::vector stride_; xla::Padding padding_; TensorFormat data_format_ = FORMAT_NHWC; + DataType reduction_type_; }; class MaxPoolOp : public PoolingOp { public: MaxPoolOp(OpKernelConstruction* ctx, int num_spatial_dims) - : PoolingOp(ctx, /*num_spatial_dims=*/num_spatial_dims) {} + : PoolingOp(ctx, /*num_spatial_dims=*/num_spatial_dims, + /*reduction_type=*/ctx->input_type(0)) {} - xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b, - DataType data_type) override { - return XlaHelpers::MinValue(b, data_type); + xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b) override { + return XlaHelpers::MinValue(b, reduction_type_); } - const xla::Computation* Reduction(XlaOpKernelContext* ctx, - DataType dtype) override { - return ctx->GetOrCreateMax(dtype); + const xla::Computation* Reduction(XlaOpKernelContext* ctx) override { + return ctx->GetOrCreateMax(reduction_type_); } xla::ComputationDataHandle PostProcessOutput( @@ -209,15 +211,17 @@ static xla::ComputationDataHandle AvgPoolDivideByCount( } // Build a matrix of all 1s, with the same width/height as the input. + const DataType accumulation_type = XlaHelpers::SumAccumulationType(dtype); auto ones = ctx->builder()->Broadcast( - XlaHelpers::One(ctx->builder(), dtype), input_dim_sizes); + XlaHelpers::One(ctx->builder(), accumulation_type), input_dim_sizes); // Perform a ReduceWindow with the same window size, strides, and padding // to count the number of contributions to each result element. - auto counts = ctx->builder()->ReduceWindow( - ones, XlaHelpers::Zero(ctx->builder(), dtype), - *ctx->GetOrCreateAdd(dtype), window_ksize, window_stride, + auto reduce = ctx->builder()->ReduceWindow( + ones, XlaHelpers::Zero(ctx->builder(), accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), window_ksize, window_stride, xla::Padding::kSame); + auto counts = XlaHelpers::ConvertElementType(ctx->builder(), reduce, dtype); return ctx->builder()->Div(output, counts, window_dims); } @@ -226,16 +230,16 @@ static xla::ComputationDataHandle AvgPoolDivideByCount( class AvgPoolOp : public PoolingOp { public: AvgPoolOp(OpKernelConstruction* ctx, int num_spatial_dims) - : PoolingOp(ctx, num_spatial_dims) {} + : PoolingOp(ctx, /*num_spatial_dims=*/num_spatial_dims, + /*reduction_type=*/ + XlaHelpers::SumAccumulationType(ctx->input_type(0))) {} - xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b, - DataType data_type) override { - return XlaHelpers::Zero(b, data_type); + xla::ComputationDataHandle InitValue(xla::ComputationBuilder* b) override { + return XlaHelpers::Zero(b, reduction_type_); } - const xla::Computation* Reduction(XlaOpKernelContext* ctx, - DataType dtype) override { - return ctx->GetOrCreateAdd(dtype); + const xla::Computation* Reduction(XlaOpKernelContext* ctx) override { + return ctx->GetOrCreateAdd(reduction_type_); } xla::ComputationDataHandle PostProcessOutput( @@ -455,14 +459,12 @@ class AvgPoolGradOp : public XlaOpKernel { gradients_shape, filter_shape, out_backprop_shape, stride_, padding_, data_format_, &dims)); + // The input gradients are computed by a convolution of the output gradients + // and the filter, with some appropriate padding. See the comment at the top + // of conv_grad_ops.h for details. + xla::ComputationBuilder* const b = ctx->builder(); auto out_backprop = ctx->Input(1); - - // The input gradients are computed by a convolution of the output - // gradients - // and the filter, with some appropriate padding. See the comment at - // the top of conv_grad_ops.h for details. - DataType dtype = input_type(1); - + auto dtype = input_type(1); xla::Padding xla_padding = (padding_ == VALID) ? xla::Padding::kValid : xla::Padding::kSame; @@ -483,17 +485,18 @@ class AvgPoolGradOp : public XlaOpKernel { padding->set_interior_padding(dims.spatial_dims[i].stride - 1); } - auto zero = XlaHelpers::Zero(ctx->builder(), dtype); - auto padded_gradients = - ctx->builder()->Pad(out_backprop_div, zero, padding_config); + auto zero = XlaHelpers::Zero(b, dtype); + auto padded_gradients = b->Pad(out_backprop_div, zero, padding_config); // in_backprop = padded_gradients ones std::vector ones(num_dims(), 1LL); - xla::ComputationDataHandle in_backprop = ctx->builder()->ReduceWindow( - padded_gradients, zero, *ctx->GetOrCreateAdd(dtype), ksize_, + auto accumulation_type = XlaHelpers::SumAccumulationType(dtype); + auto in_backprop = b->ReduceWindow( + XlaHelpers::ConvertElementType(b, padded_gradients, accumulation_type), + XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), ksize_, /* window_strides=*/ones, xla::Padding::kValid); - - ctx->SetOutput(0, in_backprop); + ctx->SetOutput(0, XlaHelpers::ConvertElementType(b, in_backprop, dtype)); } protected: diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc b/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc index 03b13b2924..812d258cd1 100644 --- a/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc @@ -27,7 +27,13 @@ namespace { class SumOp : public XlaReductionOp { public: - explicit SumOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit SumOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, + XlaHelpers::SumAccumulationType(ctx->input_type(0))) {} + xla::ComputationDataHandle InitialValue( + xla::ComputationBuilder* builder) override { + return XlaHelpers::Zero(builder, reduction_type_); + } void BuildReducer(xla::ComputationBuilder* builder, const xla::ComputationDataHandle& scalar_lhs, const xla::ComputationDataHandle& scalar_rhs) override { @@ -39,11 +45,13 @@ REGISTER_XLA_OP(Name("Sum").CompileTimeConstInput("reduction_indices"), SumOp); class ProdOp : public XlaReductionOp { public: - explicit ProdOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit ProdOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, + XlaHelpers::SumAccumulationType(ctx->input_type(0))) {} xla::ComputationDataHandle InitialValue( xla::ComputationBuilder* builder) override { - return XlaHelpers::One(builder, input_type(0)); + return XlaHelpers::One(builder, reduction_type_); } void BuildReducer(xla::ComputationBuilder* builder, @@ -58,13 +66,12 @@ REGISTER_XLA_OP(Name("Prod").CompileTimeConstInput("reduction_indices"), class MinOp : public XlaReductionOp { public: - explicit MinOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit MinOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, ctx->input_type(0)) {} xla::ComputationDataHandle InitialValue( xla::ComputationBuilder* builder) override { - xla::PrimitiveType type; - TF_CHECK_OK(DataTypeToPrimitiveType(input_type(0), &type)); - return builder->ConstantLiteral(xla::Literal::MaxValue(type)); + return XlaHelpers::MaxValue(builder, reduction_type_); } void BuildReducer(xla::ComputationBuilder* builder, @@ -78,13 +85,12 @@ REGISTER_XLA_OP(Name("Min").CompileTimeConstInput("reduction_indices"), MinOp); class MaxOp : public XlaReductionOp { public: - explicit MaxOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit MaxOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, ctx->input_type(0)) {} xla::ComputationDataHandle InitialValue( xla::ComputationBuilder* builder) override { - xla::PrimitiveType type; - TF_CHECK_OK(DataTypeToPrimitiveType(input_type(0), &type)); - return builder->ConstantLiteral(xla::Literal::MinValue(type)); + return XlaHelpers::MinValue(builder, reduction_type_); } void BuildReducer(xla::ComputationBuilder* builder, @@ -98,8 +104,14 @@ REGISTER_XLA_OP(Name("Max").CompileTimeConstInput("reduction_indices"), MaxOp); class MeanOp : public XlaReductionOp { public: - explicit MeanOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit MeanOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, + XlaHelpers::SumAccumulationType(ctx->input_type(0))) {} + xla::ComputationDataHandle InitialValue( + xla::ComputationBuilder* builder) override { + return XlaHelpers::Zero(builder, reduction_type_); + } void BuildReducer(xla::ComputationBuilder* builder, const xla::ComputationDataHandle& scalar_lhs, const xla::ComputationDataHandle& scalar_rhs) override { @@ -121,7 +133,8 @@ REGISTER_XLA_OP(Name("Mean").CompileTimeConstInput("reduction_indices"), class AllOp : public XlaReductionOp { public: - explicit AllOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit AllOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, ctx->input_type(0)) {} xla::ComputationDataHandle InitialValue( xla::ComputationBuilder* builder) override { @@ -139,7 +152,8 @@ REGISTER_XLA_OP(Name("All").CompileTimeConstInput("reduction_indices"), AllOp); class AnyOp : public XlaReductionOp { public: - explicit AnyOp(OpKernelConstruction* ctx) : XlaReductionOp(ctx) {} + explicit AnyOp(OpKernelConstruction* ctx) + : XlaReductionOp(ctx, ctx->input_type(0)) {} xla::ComputationDataHandle InitialValue( xla::ComputationBuilder* builder) override { diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops.h b/tensorflow/compiler/tf2xla/kernels/reduction_ops.h index 9aca6d8fed..f3181f0dad 100644 --- a/tensorflow/compiler/tf2xla/kernels/reduction_ops.h +++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops.h @@ -33,12 +33,12 @@ namespace tensorflow { // xla::ComputationBuilder. class XlaReductionOp : public XlaOpKernel { public: - explicit XlaReductionOp(OpKernelConstruction* ctx); + XlaReductionOp(OpKernelConstruction* ctx, DataType reduction_type); ~XlaReductionOp() override {} - // Return the base case for the reduction. Defaults to zero. + // Return the base case for the reduction. virtual xla::ComputationDataHandle InitialValue( - xla::ComputationBuilder* builder); + xla::ComputationBuilder* builder) = 0; // Implement the (scalar,scalar)->scalar lambda that should be // applied to each pair of elements to be reduced. The desired @@ -63,6 +63,9 @@ class XlaReductionOp : public XlaOpKernel { private: // True if the number of dimensions should be maintained. bool keep_dims_; + + protected: + DataType reduction_type_; }; } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc b/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc index 4b5d09eb9f..64fe765ae9 100644 --- a/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc +++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc @@ -24,19 +24,15 @@ limitations under the License. namespace tensorflow { -XlaReductionOp::XlaReductionOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { +XlaReductionOp::XlaReductionOp(OpKernelConstruction* ctx, + DataType reduction_type) + : XlaOpKernel(ctx), reduction_type_(reduction_type) { const DataType dt = BaseType(input_type(0)); OP_REQUIRES_OK(ctx, ctx->MatchSignature({dt, DT_INT32}, {dt})); OP_REQUIRES_OK(ctx, ctx->GetAttr("keep_dims", &keep_dims_)); } -// Return the base case for the reduction. Defaults to zero. -xla::ComputationDataHandle XlaReductionOp::InitialValue( - xla::ComputationBuilder* builder) { - return XlaHelpers::Zero(builder, input_type(0)); -} - // Unless BuildFinalizer is overridden the reduction has no // finalizer. xla::ComputationDataHandle XlaReductionOp::BuildFinalizer( @@ -100,36 +96,26 @@ void XlaReductionOp::Compile(XlaOpKernelContext* ctx) { string desc = ctx->op_kernel().name(); - // Call virtual method to get the initial value. - const xla::ComputationDataHandle initial = InitialValue(ctx->builder()); + xla::ComputationBuilder* const b = ctx->builder(); // Construct the builder for the reduction lambda. - xla::ComputationBuilder r(ctx->builder()->client(), - strings::StrCat(desc, "-reduction")); + xla::ComputationBuilder r(b->client(), strings::StrCat(desc, "-reduction")); xla::PrimitiveType type; - TF_CHECK_OK(DataTypeToPrimitiveType(input_type(0), &type)); - // Make two scalar parameters of the desired type for the lambda. - xla::ComputationDataHandle rx = - r.Parameter(0, xla::ShapeUtil::MakeShape(type, {}), "x"); - xla::ComputationDataHandle ry = - r.Parameter(1, xla::ShapeUtil::MakeShape(type, {}), "y"); - - auto data = ctx->Input(0); + TF_CHECK_OK(DataTypeToPrimitiveType(reduction_type_, &type)); + auto data = b->ConvertElementType(ctx->Input(0), type); + // Call virtual method to get the initial value. + auto initial = b->ConvertElementType(InitialValue(b), type); + // Make two scalar parameters of the desired type for the lambda. + auto rx = r.Parameter(0, xla::ShapeUtil::MakeShape(type, {}), "x"); + auto ry = r.Parameter(1, xla::ShapeUtil::MakeShape(type, {}), "y"); // Call virtual method to build the reduction lambda. BuildReducer(&r, rx, ry); xla::Computation reduction_computation = r.Build().ConsumeValueOrDie(); - xla::ComputationDataHandle reduce = - ctx->builder()->Reduce(data, initial, reduction_computation, xla_axes); - xla::ComputationDataHandle finalized = - BuildFinalizer(ctx->builder(), reduce, num_elements_reduced); - - xla::ComputationDataHandle result; - if (keep_dims_) { - result = ctx->builder()->Reshape(finalized, final_shape); - } else { - result = finalized; - } + auto reduce = b->Reduce(data, initial, reduction_computation, xla_axes); + auto deconverted = XlaHelpers::ConvertElementType(b, reduce, input_type(0)); + auto finalized = BuildFinalizer(b, deconverted, num_elements_reduced); + auto result = keep_dims_ ? b->Reshape(finalized, final_shape) : finalized; ctx->SetOutput(0, result); } diff --git a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc index ee4a94164c..4cfa28a0ce 100644 --- a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc @@ -66,7 +66,7 @@ class ScanOp : public XlaOpKernel { -input_shape.dims(), ", ", input_shape.dims(), "), but got ", axis)); - DataType dtype = ctx->input_type(0); + DataType dtype = XlaHelpers::SumAccumulationType(ctx->input_type(0)); if (input_shape.num_elements() == 0) { // Exit early if there is nothing to compute. @@ -91,7 +91,6 @@ class ScanOp : public XlaOpKernel { std::swap(padding[axis].first, padding[axis].second); } - xla::ComputationDataHandle input = ctx->Input(0); xla::ComputationDataHandle init; const xla::Computation* reducer; if (sum_) { @@ -102,7 +101,10 @@ class ScanOp : public XlaOpKernel { reducer = ctx->GetOrCreateMul(dtype); } auto output = builder->ReduceWindowWithGeneralPadding( - ctx->Input(0), init, *reducer, window_dims, window_strides, padding); + XlaHelpers::ConvertElementType(builder, ctx->Input(0), dtype), init, + *reducer, window_dims, window_strides, padding); + output = + XlaHelpers::ConvertElementType(builder, output, ctx->input_type(0)); // In exclusive mode, we have computed an extra element containing the sum // of all the input elements. Slice off this extra "last" element. diff --git a/tensorflow/compiler/tf2xla/kernels/softmax_op.cc b/tensorflow/compiler/tf2xla/kernels/softmax_op.cc index 750a4c2dec..aa47cb799f 100644 --- a/tensorflow/compiler/tf2xla/kernels/softmax_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/softmax_op.cc @@ -42,9 +42,8 @@ class SoftmaxOp : public XlaOpKernel { const DataType type = input_type(0); auto logits = ctx->Input(0); - xla::ComputationBuilder* b = ctx->builder(); + xla::ComputationBuilder* const b = ctx->builder(); const xla::Computation& max_func = *ctx->GetOrCreateMax(type); - const xla::Computation& add_func = *ctx->GetOrCreateAdd(type); // Find the max in each batch, resulting in a tensor of shape [batch] auto logits_max = @@ -52,21 +51,20 @@ class SoftmaxOp : public XlaOpKernel { // Subtract the max in batch b from every element in batch b. Broadcasts // along the batch dimension. auto shifted_logits = b->Sub(logits, logits_max, {kBatchDim}); - xla::ComputationDataHandle softmax; - if (log_) { - // softmax = shifted_logits - log(sum(exp(shifted_logits))) - auto log_sum_exp = - b->Log(b->Reduce(b->Exp(shifted_logits), XlaHelpers::Zero(b, type), - add_func, {kClassDim})); - softmax = b->Sub(shifted_logits, log_sum_exp, {kBatchDim}); - } else { - // softmax = exp(shifted_logits) / sum(exp(shifted_logits)) - auto exp_shifted = b->Exp(shifted_logits); - auto sum_exp = b->Reduce(exp_shifted, XlaHelpers::Zero(b, type), add_func, - {kClassDim}); - softmax = b->Div(exp_shifted, sum_exp, {kBatchDim}); - } - + auto exp_shifted = b->Exp(shifted_logits); + const DataType accumulation_type = XlaHelpers::SumAccumulationType(type); + auto converted = + XlaHelpers::ConvertElementType(b, exp_shifted, accumulation_type); + auto reduce = + b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), {kClassDim}); + auto sum = XlaHelpers::ConvertElementType(b, reduce, type); + auto softmax = + log_ + // softmax = shifted_logits - log(sum(exp(shifted_logits))) + ? b->Sub(shifted_logits, b->Log(sum), {kBatchDim}) + // softmax = exp(shifted_logits) / sum(exp(shifted_logits)) + : b->Div(exp_shifted, sum, {kBatchDim}); ctx->SetOutput(0, softmax); } @@ -82,7 +80,6 @@ CrossEntropyWithLogits(XlaOpKernelContext* ctx, DataType type, const xla::ComputationDataHandle& logits, const xla::ComputationDataHandle& labels) { const xla::Computation& max_func = *ctx->GetOrCreateMax(type); - const xla::Computation& add_func = *ctx->GetOrCreateAdd(type); const int kBatchDim = 0; const int kClassDim = 1; @@ -100,8 +97,12 @@ CrossEntropyWithLogits(XlaOpKernelContext* ctx, DataType type, auto exp_shifted_logits = b->Exp(shifted_logits); // sum_{class} (exp(logits - max_logits)) - auto sum_exp = b->Reduce(exp_shifted_logits, XlaHelpers::Zero(b, type), - add_func, {kClassDim}); + const DataType accumulation_type = XlaHelpers::SumAccumulationType(type); + auto converted = + XlaHelpers::ConvertElementType(b, exp_shifted_logits, accumulation_type); + auto reduce = b->Reduce(converted, XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), {kClassDim}); + auto sum_exp = XlaHelpers::ConvertElementType(b, reduce, type); // log(sum(exp(logits - max_logits))) auto log_sum_exp = b->Log(sum_exp); @@ -110,9 +111,13 @@ CrossEntropyWithLogits(XlaOpKernelContext* ctx, DataType type, // ((logits - max_logits) - log(sum(exp(logits - max_logits))))) // along classes // (The subtraction broadcasts along the batch dimension.) - xla::ComputationDataHandle loss = b->Reduce( - b->Mul(b->Neg(labels), b->Sub(shifted_logits, log_sum_exp, {kBatchDim})), - XlaHelpers::Zero(b, type), add_func, {kClassDim}); + auto sub = b->Sub(shifted_logits, log_sum_exp, {kBatchDim}); + auto mul = b->Mul(b->Neg(labels), sub); + auto sum = + b->Reduce(XlaHelpers::ConvertElementType(b, mul, accumulation_type), + XlaHelpers::Zero(b, accumulation_type), + *ctx->GetOrCreateAdd(accumulation_type), {kClassDim}); + auto loss = XlaHelpers::ConvertElementType(b, sum, type); // backprop: prob - labels, where // prob = exp(logits - max_logits) / sum(exp(logits - max_logits)) diff --git a/tensorflow/compiler/tf2xla/xla_helpers.cc b/tensorflow/compiler/tf2xla/xla_helpers.cc index f048662953..3b0b2f06eb 100644 --- a/tensorflow/compiler/tf2xla/xla_helpers.cc +++ b/tensorflow/compiler/tf2xla/xla_helpers.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/types.h" #include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/gtl/array_slice.h" namespace tensorflow { @@ -273,4 +274,20 @@ Status XlaHelpers::OneHot(xla::ComputationBuilder* builder, int64 depth, return Status::OK(); } +DataType XlaHelpers::SumAccumulationType(const DataType& dtype) { + if (dtype == DT_BFLOAT16) { + return DT_FLOAT; + } + return dtype; +} + +xla::ComputationDataHandle XlaHelpers::ConvertElementType( + xla::ComputationBuilder* const builder, + const xla::ComputationDataHandle& operand, + const DataType new_element_type) { + xla::PrimitiveType convert_to; + TF_CHECK_OK(DataTypeToPrimitiveType(new_element_type, &convert_to)); + return builder->ConvertElementType(operand, convert_to); +} + } // end namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/xla_helpers.h b/tensorflow/compiler/tf2xla/xla_helpers.h index 2a027db4c8..68ab93b64a 100644 --- a/tensorflow/compiler/tf2xla/xla_helpers.h +++ b/tensorflow/compiler/tf2xla/xla_helpers.h @@ -107,6 +107,18 @@ class XlaHelpers { const xla::ComputationDataHandle& on_value, const xla::ComputationDataHandle& off_value, xla::ComputationDataHandle* one_hot); + + // Certain DataTypes should use increased precision DataTypes when performing + // reductions. This function remaps a given DataType to a higher precision + // DataType if needed. + static DataType SumAccumulationType(const DataType& dtype); + + // A helper for creating a ConvertElementType xla op given a DataType rather + // than the xla::PrimitiveType. + static xla::ComputationDataHandle ConvertElementType( + xla::ComputationBuilder* const builder, + const xla::ComputationDataHandle& operand, + const DataType new_element_type); }; } // end namespace tensorflow diff --git a/tensorflow/compiler/xla/literal_util.cc b/tensorflow/compiler/xla/literal_util.cc index 20508edaa7..214c2030cd 100644 --- a/tensorflow/compiler/xla/literal_util.cc +++ b/tensorflow/compiler/xla/literal_util.cc @@ -1463,6 +1463,9 @@ StatusOr> ConvertIfDestTypeMatches( StatusOr> Literal::Convert( PrimitiveType primitive_dest_type) const { TF_RET_CHECK(ShapeUtil::IsArray(shape())); + if (shape().element_type() == primitive_dest_type) { + return CloneToUnique(); + } switch (shape().element_type()) { #define CONVERT_IF_DEST_TYPE_MATCHES(type) \ case (type): \ diff --git a/tensorflow/compiler/xla/tests/convert_test.cc b/tensorflow/compiler/xla/tests/convert_test.cc index 7926767a4f..9a899b7914 100644 --- a/tensorflow/compiler/xla/tests/convert_test.cc +++ b/tensorflow/compiler/xla/tests/convert_test.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/compiler/xla/tests/test_macros.h" #include "tensorflow/compiler/xla/xla_data.pb.h" #include "tensorflow/core/lib/core/casts.h" +#include "tensorflow/core/lib/math/math_util.h" #include "tensorflow/core/platform/stream_executor_no_cuda.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/types.h" @@ -384,5 +385,44 @@ XLA_TEST_F(ConvertTest, ConvertR1F32ToR1F16) { ComputeAndCompareR1(&builder, expected_output, {dot_lhs_handle.get()}); } + +XLA_TEST_F(ConvertTest, ConvertC64ToC64) { + ComputationBuilder builder(client_, TestName()); + std::vector x = {{42.0f, 64.0f}}; + builder.ConvertElementType(builder.ConstantR1(x), C64); + ComputeAndCompareR1(&builder, x, {}, ErrorSpec(0.0001)); +} + +XLA_TEST_F(ConvertTest, ConvertS64S64) { + ComputationBuilder builder(client_, TestName()); + std::vector x = {{-42, 64}}; + builder.ConvertElementType(builder.ConstantR1(x), S64); + ComputeAndCompareR1(&builder, x, {}); +} + +XLA_TEST_F(ConvertTest, ConvertU64U64) { + ComputationBuilder builder(client_, TestName()); + std::vector x = {{42, 64}}; + builder.ConvertElementType(builder.ConstantR1(x), U64); + ComputeAndCompareR1(&builder, x, {}); +} + +XLA_TEST_F(ConvertTest, ConvertU64S64) { + ComputationBuilder builder(client_, TestName()); + std::vector unsigned_x = {{42, UINT64_MAX}}; + builder.ConvertElementType(builder.ConstantR1(unsigned_x), S64); + std::vector signed_x = {{42, -1}}; + ComputeAndCompareR1(&builder, signed_x, {}); +} + +XLA_TEST_F(ConvertTest, ConvertS64U64) { + ComputationBuilder builder(client_, TestName()); + std::vector signed_x = {{42, -1, INT64_MIN}}; + builder.ConvertElementType(builder.ConstantR1(signed_x), U64); + std::vector unsigned_x = { + {42, UINT64_MAX, tensorflow::MathUtil::IPow(2, 63)}}; + ComputeAndCompareR1(&builder, unsigned_x, {}); +} + } // namespace } // namespace xla -- GitLab From 942a32bc71291994c14625b6311268319dd27808 Mon Sep 17 00:00:00 2001 From: James Qin Date: Wed, 21 Mar 2018 15:55:30 -0700 Subject: [PATCH 050/906] Change Softmax on CUDA to use fp32 for denominator when input/output are fp16. This avoids potential overflow in the denominator, also makes sure accumulation is done in high precision. PiperOrigin-RevId: 189982655 --- tensorflow/core/kernels/softmax_op_gpu.cu.cc | 90 ++++++++++++++----- tensorflow/python/framework/test_util.py | 75 +++++++++------- tensorflow/python/kernel_tests/BUILD | 2 +- .../python/kernel_tests/softmax_op_test.py | 38 ++++++-- 4 files changed, 145 insertions(+), 60 deletions(-) diff --git a/tensorflow/core/kernels/softmax_op_gpu.cu.cc b/tensorflow/core/kernels/softmax_op_gpu.cu.cc index 1f4a82a733..130d693dbd 100644 --- a/tensorflow/core/kernels/softmax_op_gpu.cu.cc +++ b/tensorflow/core/kernels/softmax_op_gpu.cu.cc @@ -33,8 +33,42 @@ namespace tensorflow { namespace { +template +__device__ __host__ EIGEN_STRONG_INLINE + typename std::enable_if::value, U>::type + strict_cast(T t); + +template +__device__ __host__ EIGEN_STRONG_INLINE + typename std::enable_if::value, U>::type + strict_cast(T t) { + return t; +} + +template <> +__device__ __host__ EIGEN_STRONG_INLINE float strict_cast( + Eigen::half t) { + return functor::HalfToFloat()(t); +} + +template <> +__device__ __host__ EIGEN_STRONG_INLINE Eigen::half +strict_cast(float t) { + return functor::FloatToHalf()(t); +} + template -__global__ void GenerateNormalizedProb(const T* logits, const T* sum_probs, +struct softmax_traits { + using accumulator_type = T; +}; + +template <> +struct softmax_traits { + using accumulator_type = float; +}; + +template +__global__ void GenerateNormalizedProb(const T* logits, const U* sum_probs, const T* max_logits, T* output, const int num_rows, const int num_cols, const bool in_log_space) { @@ -43,25 +77,33 @@ __global__ void GenerateNormalizedProb(const T* logits, const T* sum_probs, const int row = tid / num_cols; const int col = tid % num_cols; + // TODO(jamesqin): change to half2 load when inputs are Eigen::half. + U input = strict_cast(logits[tid]); + U max_val = strict_cast(ldg(max_logits + row)); + U result; + if (row < num_rows && col < num_cols) { - if (in_log_space) - output[tid] = - logits[tid] - ldg(max_logits + row) - log(ldg(sum_probs + row)); - else - output[tid] = - exp(logits[tid] - ldg(max_logits + row)) / ldg(sum_probs + row); + if (in_log_space) { + result = input - max_val - log(ldg(sum_probs + row)); + } else { + result = exp(input - max_val) / ldg(sum_probs + row); + } + output[tid] = strict_cast(result); } } -template +template struct SubtractAndExpFunctor { __host__ __device__ SubtractAndExpFunctor(const T* logits, const T* max_logits, const int num_cols) : logits_(logits), max_logits_(max_logits), num_cols_(num_cols) {} - __host__ __device__ T operator()(const int gid) const { - return exp(logits_[gid] - ldg(max_logits_ + gid / num_cols_)); + __host__ __device__ U operator()(const int gid) const { + // TODO(jamesqin): change to half2 load when inputs are Eigen::half. + const U diff = + strict_cast(logits_[gid] - ldg(max_logits_ + gid / num_cols_)); + return exp(diff); } const T* logits_; @@ -80,7 +122,6 @@ void DoRowReduction(OpKernelContext* context, T* output, InputIter input, functor::ReduceImpl( context, output, input, 2, rows, cols, 1, 1, constants.kOne, op); } - } // namespace template @@ -108,8 +149,10 @@ class SoftmaxOpGPU : public OpKernel { OP_REQUIRES_OK(context, context->allocate_temp(DataTypeToEnum::value, softmax_out->shape(), &max_logits)); + + typedef typename softmax_traits::accumulator_type acc_type; OP_REQUIRES_OK(context, - context->allocate_temp(DataTypeToEnum::value, + context->allocate_temp(DataTypeToEnum::value, softmax_out->shape(), &sum_probs)); DoRowReduction( @@ -120,25 +163,28 @@ class SoftmaxOpGPU : public OpKernel { const int numBlocks = Eigen::divup(rows * cols, numThreads); cub::CountingInputIterator counting_iterator(0); - typedef cub::TransformInputIterator, + typedef cub::TransformInputIterator, cub::CountingInputIterator> InputIterType; InputIterType input_itr( counting_iterator, - SubtractAndExpFunctor( + SubtractAndExpFunctor( reinterpret_cast(logits_in_.flat().data()), reinterpret_cast(max_logits.flat().data()), cols)); - DoRowReduction( - context, const_cast(sum_probs.flat().data()), input_itr, rows, - cols); + DoRowReduction( + context, const_cast(sum_probs.flat().data()), + input_itr, rows, cols); - GenerateNormalizedProb<<>>( - reinterpret_cast(logits_in_.flat().data()), - reinterpret_cast(sum_probs.flat().data()), - reinterpret_cast(max_logits.flat().data()), - const_cast(softmax_out->flat().data()), rows, cols, log_); + GenerateNormalizedProb + <<>>( + reinterpret_cast(logits_in_.flat().data()), + reinterpret_cast( + sum_probs.flat().data()), + reinterpret_cast(max_logits.flat().data()), + const_cast(softmax_out->flat().data()), rows, cols, log_); } } diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py index d8f8569939..43106b6e59 100644 --- a/tensorflow/python/framework/test_util.py +++ b/tensorflow/python/framework/test_util.py @@ -53,6 +53,7 @@ from tensorflow.python.eager import tape # pylint: disable=unused-import from tensorflow.python.framework import device as pydev from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors +from tensorflow.python.framework import errors_impl from tensorflow.python.framework import importer from tensorflow.python.framework import ops from tensorflow.python.framework import random_seed @@ -201,6 +202,7 @@ def _strip_checkpoint_v2_randomized(graph_def): def IsGoogleCudaEnabled(): return pywrap_tensorflow.IsGoogleCudaEnabled() + def CudaSupportsHalfMatMulAndConv(): return pywrap_tensorflow.CudaSupportsHalfMatMulAndConv() @@ -335,6 +337,8 @@ def _use_c_api_wrapper(fn, use_c_api, *args, **kwargs): # Make sure default graph reflects prev_value in case next test doesn't call # reset_default_graph(). ops.reset_default_graph() + + # pylint: disable=protected-access @@ -451,7 +455,8 @@ def with_c_api(cls): # If the C API is already enabled, don't do anything. Some tests break if the # same test is run twice, so this allows us to turn on the C API by default # without breaking these tests. - if ops._USE_C_API: return cls + if ops._USE_C_API: + return cls for name, value in cls.__dict__.copy().items(): if callable(value) and name.startswith("test"): @@ -469,6 +474,7 @@ def assert_no_new_pyobjects_executing_eagerly(f): Useful for checking that there are no missing Py_DECREFs in the C exercised by a bit of Python. """ + def decorator(self, **kwargs): """Warms up, gets an object count, runs the test, checks for new objects.""" with context.eager_mode(): @@ -483,8 +489,10 @@ def assert_no_new_pyobjects_executing_eagerly(f): new_count = len(gc.get_objects()) self.assertEqual(previous_count, new_count) gc.enable() + return decorator + def assert_no_new_tensors(f): """Decorator for asserting that no new Tensors persist after a test. @@ -508,17 +516,15 @@ def assert_no_new_tensors(f): def _is_tensorflow_object(obj): try: - return isinstance(obj, ( - ops.Tensor, - variables.Variable, - tensor_shape.Dimension, - tensor_shape.TensorShape)) + return isinstance(obj, + (ops.Tensor, variables.Variable, + tensor_shape.Dimension, tensor_shape.TensorShape)) except ReferenceError: # If the object no longer exists, we don't care about it. return False - tensors_before = set(id(obj) for obj in gc.get_objects() - if _is_tensorflow_object(obj)) + tensors_before = set( + id(obj) for obj in gc.get_objects() if _is_tensorflow_object(obj)) outside_graph_key = ops.get_default_graph()._graph_key with ops.Graph().as_default(): # Run the test in a new graph so that collections get cleared when it's @@ -572,18 +578,18 @@ def assert_no_garbage_created(f): "likely due to a reference cycle. New objects in cycle(s):") for i, obj in enumerate(gc.garbage[previous_garbage:]): try: - logging.error( - "Object %d of %d" % (i, len(gc.garbage) - previous_garbage)) + logging.error("Object %d of %d", i, + len(gc.garbage) - previous_garbage) + def _safe_object_str(obj): return "<%s %d>" % (obj.__class__.__name__, id(obj)) - logging.error(" Object type: %s" % (_safe_object_str(obj),)) - logging.error(" Referrer types: %s" % ( - ', '.join([_safe_object_str(ref) - for ref in gc.get_referrers(obj)]),)) - logging.error(" Referent types: %s" % ( - ', '.join([_safe_object_str(ref) - for ref in gc.get_referents(obj)]),)) - logging.error(" Object attribute names: %s" % (dir(obj),)) + + logging.error(" Object type: %s", _safe_object_str(obj)) + logging.error(" Referrer types: %s", ", ".join( + [_safe_object_str(ref) for ref in gc.get_referrers(obj)])) + logging.error(" Referent types: %s", ", ".join( + [_safe_object_str(ref) for ref in gc.get_referents(obj)])) + logging.error(" Object attribute names: %s", dir(obj)) logging.error(" Object __str__:") logging.error(obj) logging.error(" Object __repr__:") @@ -705,15 +711,23 @@ def is_gpu_available(cuda_only=False, min_cuda_compute_capability=None): return 0, 0 return int(match.group(1)), int(match.group(2)) - for local_device in device_lib.list_local_devices(): - if local_device.device_type == "GPU": - if (min_cuda_compute_capability is None or - compute_capability_from_device_desc(local_device.physical_device_desc) - >= min_cuda_compute_capability): + try: + for local_device in device_lib.list_local_devices(): + if local_device.device_type == "GPU": + if (min_cuda_compute_capability is None or + compute_capability_from_device_desc( + local_device.physical_device_desc) >= + min_cuda_compute_capability): + return True + if local_device.device_type == "SYCL" and not cuda_only: return True - if local_device.device_type == "SYCL" and not cuda_only: - return True - return False + return False + except errors_impl.NotFoundError as e: + if not all([x in str(e) for x in ["CUDA", "not find"]]): + raise e + else: + logging.error(str(e)) + return False @contextlib.contextmanager @@ -1256,9 +1270,9 @@ class TensorFlowTestCase(googletest.TestCase): msg="Mismatched value: a%s is different from b%s." % (path_str, path_str)) except TypeError as e: - msg = "Error: a%s has %s, but b%s has %s" % ( - path_str, type(a), path_str, type(b)) - e.args = ((e.args[0] + ' : ' + msg,) + e.args[1:]) + msg = "Error: a%s has %s, but b%s has %s" % (path_str, type(a), + path_str, type(b)) + e.args = ((e.args[0] + " : " + msg,) + e.args[1:]) raise def assertAllClose(self, a, b, rtol=1e-6, atol=1e-6, msg=None): @@ -1438,8 +1452,7 @@ class TensorFlowTestCase(googletest.TestCase): """ device1 = pydev.canonical_name(device1) device2 = pydev.canonical_name(device2) - self.assertEqual(device1, device2, - "Devices %s and %s are not equal. %s" % + self.assertEqual(device1, device2, "Devices %s and %s are not equal. %s" % (device1, device2, msg)) # Fix Python 3 compatibility issues diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index d9571fa2be..ece1da0332 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1910,7 +1910,7 @@ cuda_py_test( cuda_py_test( name = "softmax_op_test", - size = "small", + size = "medium", srcs = ["softmax_op_test.py"], additional_deps = [ "//third_party/py/numpy", diff --git a/tensorflow/python/kernel_tests/softmax_op_test.py b/tensorflow/python/kernel_tests/softmax_op_test.py index 2b8e99e18e..981f96b74d 100644 --- a/tensorflow/python/kernel_tests/softmax_op_test.py +++ b/tensorflow/python/kernel_tests/softmax_op_test.py @@ -18,14 +18,17 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import unittest import numpy as np + from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors_impl from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import nn_ops from tensorflow.python.platform import test +from tensorflow.python.platform import tf_logging as logging @test_util.with_c_api @@ -41,9 +44,10 @@ class SoftmaxTest(test.TestCase): features, axis=dim), one_only_on_dim)) softmax = e / np.reshape(np.sum(e, axis=dim), one_only_on_dim) if log: - return np.log(softmax) + res = np.log(softmax) else: - return softmax + res = softmax + return res def _testSoftmax(self, np_features, dim=-1, log=False, use_gpu=False): # A previous version of the code checked the op name rather than the op type @@ -53,9 +57,9 @@ class SoftmaxTest(test.TestCase): np_softmax = self._npSoftmax(np_features, dim=dim, log=log) with self.test_session(use_gpu=use_gpu): if log: - tf_softmax = nn_ops.log_softmax(np_features, dim=dim, name=name) + tf_softmax = nn_ops.log_softmax(np_features, axis=dim, name=name) else: - tf_softmax = nn_ops.softmax(np_features, dim=dim, name=name) + tf_softmax = nn_ops.softmax(np_features, axis=dim, name=name) out = tf_softmax.eval() self.assertAllCloseAccordingToType(np_softmax, out) self.assertShapeEqual(np_softmax, tf_softmax) @@ -117,10 +121,32 @@ class SoftmaxTest(test.TestCase): self._testAll( np.array([[1., 1., 1., 1.], [1., 2., 3., 4.]]).astype(np.float32)) + @unittest.skipUnless(test.is_built_with_cuda(), + "Test only applicable when running on GPUs") + def testFloatGPU(self): + if test.is_gpu_available(cuda_only=True): + rows = [2**x + np.random.randint(0, 1024) for x in range(1, 10)] + cols = [2**x + np.random.randint(0, 1024) for x in range(1, 10)] + for row, col in zip(rows, cols): + logging.info("Testing softmax float dtype in shape [%d, %d]", row, col) + data = np.random.rand(row, col) + self._testAll(data.astype(np.float32)) + def testHalf(self): self._testAll( np.array([[1., 1., 1., 1.], [1., 2., 3., 4.]]).astype(np.float16)) + @unittest.skipUnless(test.is_built_with_cuda(), + "Test only applicable when running on GPUs") + def testHalfGPU(self): + if test.is_gpu_available(cuda_only=True): + rows = [2**x + np.random.randint(0, 1024) for x in range(1, 8)] + cols = [2**x + np.random.randint(0, 1024) for x in range(1, 8)] + for row, col in zip(rows, cols): + logging.info("Testing softmax half dtype in shape [%d, %d]", row, col) + data = np.random.rand(row, col) + self._testAll(data.astype(np.float16)) + def testDouble(self): self._testSoftmax( np.array([[1., 1., 1., 1.], [1., 2., 3., 4.]]).astype(np.float64)) @@ -169,7 +195,7 @@ class SoftmaxTest(test.TestCase): self.assertEqual(0, array_ops.size(x).eval()) # reshape would raise if logits is empty with self.assertRaises(errors_impl.InvalidArgumentError): - nn_ops.softmax(x, dim=0).eval() + nn_ops.softmax(x, axis=0).eval() def testDimTooLarge(self): with self.test_session(): @@ -177,7 +203,7 @@ class SoftmaxTest(test.TestCase): # inference error. dim = array_ops.placeholder_with_default(100, shape=[]) with self.assertRaises(errors_impl.InvalidArgumentError): - nn_ops.softmax([1., 2., 3., 4.], dim=dim).eval() + nn_ops.softmax([1., 2., 3., 4.], axis=dim).eval() def testLargeDims(self): # Make sure that we properly handle large inputs. See -- GitLab From 1f58c96b593c49a97bbfac0665c2628ef9c910cd Mon Sep 17 00:00:00 2001 From: Max Galkin Date: Wed, 21 Mar 2018 16:00:44 -0700 Subject: [PATCH 051/906] Shorter definitions for elementwise_ops in op_level_cost_estimator. PiperOrigin-RevId: 189983460 --- .../grappler/costs/op_level_cost_estimator.cc | 180 +++++++----------- 1 file changed, 67 insertions(+), 113 deletions(-) diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc index d3ffa03fe2..fdbc61f3f1 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc @@ -192,121 +192,75 @@ OpLevelCostEstimator::OpLevelCostEstimator() { {kShape, wrap(&OpLevelCostEstimator::PredictMetadata)}, {kSize, wrap(&OpLevelCostEstimator::PredictMetadata)}}; +#define EIGEN_COST(X) Eigen::internal::functor_traits::Cost + // Quantize = apply min and max bounds, multiply by scale factor and round. const int quantize_v2_cost = - Eigen::internal::functor_traits< - Eigen::internal::scalar_product_op>::Cost + - Eigen::internal::functor_traits< - Eigen::internal::scalar_max_op>::Cost + - Eigen::internal::functor_traits< - Eigen::internal::scalar_min_op>::Cost + - Eigen::internal::functor_traits< - Eigen::internal::scalar_round_op>::Cost; - - elementwise_ops_ = { - // Unary ops alphabetically sorted - {"Acos", Eigen::internal::functor_traits< - Eigen::internal::scalar_acos_op>::Cost}, - {"Asin", Eigen::internal::functor_traits< - Eigen::internal::scalar_asin_op>::Cost}, - {"Atan", Eigen::internal::functor_traits< - Eigen::internal::scalar_atan_op>::Cost}, - {"Atan2", Eigen::internal::functor_traits< - Eigen::internal::scalar_quotient_op>::Cost + - Eigen::internal::functor_traits< - Eigen::internal::scalar_atan_op>::Cost}, - {"Ceil", Eigen::internal::functor_traits< - Eigen::internal::scalar_ceil_op>::Cost}, - {"Cos", Eigen::internal::functor_traits< - Eigen::internal::scalar_cos_op>::Cost}, - {"Dequantize", Eigen::internal::functor_traits< - Eigen::internal::scalar_product_op>::Cost}, - {"Erf", 1}, - {"Erfc", 1}, - {"Exp", Eigen::internal::functor_traits< - Eigen::internal::scalar_exp_op>::Cost}, - {"Expm1", Eigen::internal::functor_traits< - Eigen::internal::scalar_expm1_op>::Cost}, - {"Floor", Eigen::internal::functor_traits< - Eigen::internal::scalar_floor_op>::Cost}, - {"Inv", Eigen::internal::functor_traits< - Eigen::internal::scalar_inverse_op>::Cost}, - {"InvGrad", 1}, - {"Lgamma", 1}, - {"Log", Eigen::internal::functor_traits< - Eigen::internal::scalar_log_op>::Cost}, - {"Log1p", Eigen::internal::functor_traits< - Eigen::internal::scalar_log1p_op>::Cost}, - {"Neg", Eigen::internal::functor_traits< - Eigen::internal::scalar_opposite_op>::Cost}, - {"QuantizeV2", quantize_v2_cost}, - {"Reciprocal", Eigen::internal::functor_traits< - Eigen::internal::scalar_inverse_op>::Cost}, - {"Rint", 1}, - {"Round", Eigen::internal::functor_traits< - Eigen::internal::scalar_round_op>::Cost}, - {"Rsqrt", Eigen::internal::functor_traits< - Eigen::internal::scalar_rsqrt_op>::Cost}, - {"Sqrt", Eigen::internal::functor_traits< - Eigen::internal::scalar_sqrt_op>::Cost}, - {"Square", Eigen::internal::functor_traits< - Eigen::internal::scalar_square_op>::Cost}, - {"Tanh", Eigen::internal::functor_traits< - Eigen::internal::scalar_tanh_op>::Cost}, - {"Relu", Eigen::internal::functor_traits< - Eigen::internal::scalar_max_op>::Cost}, - {"Sigmoid", Eigen::internal::functor_traits< - Eigen::internal::scalar_sigmoid_op>::Cost}, - {"Sign", Eigen::internal::functor_traits< - Eigen::internal::scalar_sign_op>::Cost}, - {"Sin", Eigen::internal::functor_traits< - Eigen::internal::scalar_sin_op>::Cost}, - {"Tan", Eigen::internal::functor_traits< - Eigen::internal::scalar_tan_op>::Cost}, - // Binary ops alphabetically sorted - {"Add", Eigen::internal::functor_traits< - Eigen::internal::scalar_sum_op>::Cost}, - {"ApproximateEqual", 1}, - {"BiasAdd", Eigen::internal::functor_traits< - Eigen::internal::scalar_sum_op>::Cost}, - {"Div", Eigen::internal::functor_traits< - Eigen::internal::scalar_quotient_op>::Cost}, - {"Equal", 1}, - {"FloorDiv", Eigen::internal::functor_traits< - Eigen::internal::scalar_quotient_op>::Cost}, - {"FloorMod", Eigen::internal::functor_traits< - Eigen::internal::scalar_mod_op>::Cost}, - {"Greater", 1}, - {"GreaterEqual", 1}, - {"Less", 1}, - {"LessEqual", 1}, - {"LogicalAnd", Eigen::internal::functor_traits< - Eigen::internal::scalar_boolean_and_op>::Cost}, - {"LogicalNot", 1}, - {"LogicalOr", Eigen::internal::functor_traits< - Eigen::internal::scalar_boolean_or_op>::Cost}, - {"Maximum", Eigen::internal::functor_traits< - Eigen::internal::scalar_max_op>::Cost}, - {"Minimum", Eigen::internal::functor_traits< - Eigen::internal::scalar_min_op>::Cost}, - {"Mod", Eigen::internal::functor_traits< - Eigen::internal::scalar_mod_op>::Cost}, - {"Mul", Eigen::internal::functor_traits< - Eigen::internal::scalar_product_op>::Cost}, - {"NotEqual", 1}, - {"QuantizedAdd", Eigen::internal::functor_traits< - Eigen::internal::scalar_sum_op>::Cost}, - {"QuantizedMul", Eigen::internal::functor_traits< - Eigen::internal::scalar_product_op>::Cost}, - {"RealDiv", Eigen::internal::functor_traits< - Eigen::internal::scalar_quotient_op>::Cost}, - {"SquareDifference", 1}, - {"Sub", Eigen::internal::functor_traits< - Eigen::internal::scalar_difference_op>::Cost}, - {"TruncateDiv", Eigen::internal::functor_traits< - Eigen::internal::scalar_quotient_op>::Cost}, - {"TruncateMod", Eigen::internal::functor_traits< - Eigen::internal::scalar_mod_op>::Cost}}; + EIGEN_COST(scalar_product_op) + EIGEN_COST(scalar_max_op) + + EIGEN_COST(scalar_min_op) + EIGEN_COST(scalar_round_op); + + elementwise_ops_ = {// Unary ops alphabetically sorted + {"Acos", EIGEN_COST(scalar_acos_op)}, + {"Asin", EIGEN_COST(scalar_asin_op)}, + {"Atan", EIGEN_COST(scalar_atan_op)}, + {"Atan2", EIGEN_COST(scalar_quotient_op) + + EIGEN_COST(scalar_atan_op)}, + {"Ceil", EIGEN_COST(scalar_ceil_op)}, + {"Cos", EIGEN_COST(scalar_cos_op)}, + {"Dequantize", EIGEN_COST(scalar_product_op)}, + {"Erf", 1}, + {"Erfc", 1}, + {"Exp", EIGEN_COST(scalar_exp_op)}, + {"Expm1", EIGEN_COST(scalar_expm1_op)}, + {"Floor", EIGEN_COST(scalar_floor_op)}, + {"Inv", EIGEN_COST(scalar_inverse_op)}, + {"InvGrad", 1}, + {"Lgamma", 1}, + {"Log", EIGEN_COST(scalar_log_op)}, + {"Log1p", EIGEN_COST(scalar_log1p_op)}, + {"Neg", EIGEN_COST(scalar_opposite_op)}, + {"QuantizeV2", quantize_v2_cost}, + {"Reciprocal", EIGEN_COST(scalar_inverse_op)}, + {"Rint", 1}, + {"Round", EIGEN_COST(scalar_round_op)}, + {"Rsqrt", EIGEN_COST(scalar_rsqrt_op)}, + {"Sqrt", EIGEN_COST(scalar_sqrt_op)}, + {"Square", EIGEN_COST(scalar_square_op)}, + {"Tanh", EIGEN_COST(scalar_tanh_op)}, + {"Relu", EIGEN_COST(scalar_max_op)}, + {"Sigmoid", EIGEN_COST(scalar_sigmoid_op)}, + {"Sign", EIGEN_COST(scalar_sign_op)}, + {"Sin", EIGEN_COST(scalar_sin_op)}, + {"Tan", EIGEN_COST(scalar_tan_op)}, + // Binary ops alphabetically sorted + {"Add", EIGEN_COST(scalar_sum_op)}, + {"ApproximateEqual", 1}, + {"BiasAdd", EIGEN_COST(scalar_sum_op)}, + {"Div", EIGEN_COST(scalar_quotient_op)}, + {"Equal", 1}, + {"FloorDiv", EIGEN_COST(scalar_quotient_op)}, + {"FloorMod", EIGEN_COST(scalar_mod_op)}, + {"Greater", 1}, + {"GreaterEqual", 1}, + {"Less", 1}, + {"LessEqual", 1}, + {"LogicalAnd", EIGEN_COST(scalar_boolean_and_op)}, + {"LogicalNot", 1}, + {"LogicalOr", EIGEN_COST(scalar_boolean_or_op)}, + {"Maximum", EIGEN_COST(scalar_max_op)}, + {"Minimum", EIGEN_COST(scalar_min_op)}, + {"Mod", EIGEN_COST(scalar_mod_op)}, + {"Mul", EIGEN_COST(scalar_product_op)}, + {"NotEqual", 1}, + {"QuantizedAdd", EIGEN_COST(scalar_sum_op)}, + {"QuantizedMul", EIGEN_COST(scalar_product_op)}, + {"RealDiv", EIGEN_COST(scalar_quotient_op)}, + {"SquareDifference", 1}, + {"Sub", EIGEN_COST(scalar_difference_op)}, + {"TruncateDiv", EIGEN_COST(scalar_quotient_op)}, + {"TruncateMod", EIGEN_COST(scalar_mod_op)}}; + +#undef EIGEN_COST // By default, use sum of memory_time and compute_time for execution_time. compute_memory_overlap_ = false; -- GitLab From 637b090ea0a5029805ba5e1dcf41c3b57d944ae4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 21 Mar 2018 17:34:52 -0700 Subject: [PATCH 052/906] Small convenience changes. PiperOrigin-RevId: 189996801 --- tensorflow/contrib/py2tf/pyct/compiler.py | 2 +- tensorflow/contrib/py2tf/pyct/transformer.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/py2tf/pyct/compiler.py b/tensorflow/contrib/py2tf/pyct/compiler.py index 507dbc7ed3..24c4517afa 100644 --- a/tensorflow/contrib/py2tf/pyct/compiler.py +++ b/tensorflow/contrib/py2tf/pyct/compiler.py @@ -31,7 +31,7 @@ import astor import gast -def ast_to_source(node, indentation): +def ast_to_source(node, indentation=' '): """Return the source code of given AST.""" if isinstance(node, gast.AST): node = gast.gast_to_ast(node) diff --git a/tensorflow/contrib/py2tf/pyct/transformer.py b/tensorflow/contrib/py2tf/pyct/transformer.py index 57016bb4ce..31ef7e1c05 100644 --- a/tensorflow/contrib/py2tf/pyct/transformer.py +++ b/tensorflow/contrib/py2tf/pyct/transformer.py @@ -24,6 +24,7 @@ import gast import six from tensorflow.contrib.py2tf.pyct import anno +from tensorflow.contrib.py2tf.pyct import compiler from tensorflow.contrib.py2tf.pyct import pretty_printer @@ -31,6 +32,13 @@ class PyFlowParseError(SyntaxError): pass +def try_ast_to_source(node): + try: + return compiler.ast_to_source(node) + except AssertionError: + return '' + + class Base(gast.NodeTransformer): """Base class for specialized transformers.""" @@ -62,8 +70,9 @@ class Base(gast.NodeTransformer): return super(Base, self).visit(node) except (ValueError, AttributeError, KeyError, NotImplementedError, AssertionError) as e: - msg = '%s: %s\nOccurred at node:\n%s' % ( - e.__class__.__name__, str(e), pretty_printer.fmt(node, color=False)) + msg = '%s: %s\nOffending source:\n%s\n\nOccurred at node:\n%s' % ( + e.__class__.__name__, str(e), try_ast_to_source(node), + pretty_printer.fmt(node, color=False)) if source_code: line = source_code.splitlines()[self._lineno - 1] else: -- GitLab From 8e4e9f7ceaa78f76b7f0aaa7a607e80e67f0d912 Mon Sep 17 00:00:00 2001 From: Mingsheng Hong Date: Wed, 21 Mar 2018 17:38:04 -0700 Subject: [PATCH 053/906] Added an experimental C API to dump TF_Graph in a human-readable format, for debugging purposes. PiperOrigin-RevId: 189997099 --- tensorflow/c/c_api_experimental.cc | 10 ++++++++++ tensorflow/c/c_api_experimental.h | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc index eb17e16d3e..34b9dec3ee 100644 --- a/tensorflow/c/c_api_experimental.cc +++ b/tensorflow/c/c_api_experimental.cc @@ -483,3 +483,13 @@ void TF_ShutdownTPUExecution(TF_Session* session, TF_Output shutdown_node, /*targets*/ &shutdown_node.oper, /*ntargets*/ 1, /*run_metadata*/ nullptr, status); } + +TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, + size_t* len) { + tensorflow::mutex_lock c(graph->mu); + const auto& debug_str = graph->graph.ToGraphDefDebug().DebugString(); + *len = debug_str.size(); + char* ret = static_cast(malloc(*len + 1)); + memcpy(ret, debug_str.c_str(), *len + 1); + return ret; +} diff --git a/tensorflow/c/c_api_experimental.h b/tensorflow/c/c_api_experimental.h index 2bad278d63..b95cdfe6aa 100644 --- a/tensorflow/c/c_api_experimental.h +++ b/tensorflow/c/c_api_experimental.h @@ -94,6 +94,12 @@ TF_CAPI_EXPORT extern void TF_ShutdownTPUExecution(TF_Session* session, TF_Output shutdown_node, TF_Status* status); +// Returns the graph content in a human-readable format, with length set in +// `len`. The format is subject to change in the future. +// The returned string is heap-allocated, and caller should call free() on it. +TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, + size_t* len); + #ifdef __cplusplus } /* end extern "C" */ #endif -- GitLab From c7334fef9d1173525f6111b8ab50360b6531d76b Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Wed, 21 Mar 2018 18:02:01 -0700 Subject: [PATCH 054/906] [tf.data] Do not crash when combining .cache().take().repeat() Currently, if the .cache() iterator is not fully consumed before being repeated, it will cause an exception to be raised to Python. Instead, cache should act as an identity transformation and log an error, as this will not affect the correctness of the user's program (at the cost of an unexpected performance cost: i.e. not actually caching). PiperOrigin-RevId: 189999552 --- .../core/kernels/data/cache_dataset_ops.cc | 17 ++++++++++++++++- .../data/kernel_tests/cache_dataset_op_test.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/data/cache_dataset_ops.cc b/tensorflow/core/kernels/data/cache_dataset_ops.cc index f0a2192826..4b4728dab6 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops.cc @@ -308,6 +308,21 @@ class CacheDatasetOp : public UnaryDatasetOpKernel { input_impl_(params.dataset->input_->MakeIterator(params.prefix)), cache_(new std::vector>) {} + ~MemoryWriterIterator() override { + mutex_lock l(mu_); + if (cache_) { + LOG(ERROR) + << "The calling iterator did not fully read the dataset we were " + "attempting to cache. In order to avoid unexpected truncation " + "of the sequence, the current [partially cached] sequence " + "will be dropped. This can occur if you have a sequence " + "similar to `dataset.cache().take(k).repeat()`. Instead, swap " + "the order (i.e. `dataset.take(k).cache().repeat()`)"; + mutex_lock l2(dataset()->mu_); + dataset()->writer_iterator_created_ = false; + } + } + Status GetNextInternal(IteratorContext* ctx, std::vector* out_tensors, bool* end_of_sequence) override { @@ -318,7 +333,7 @@ class CacheDatasetOp : public UnaryDatasetOpKernel { // Guard on cache_ to not crash if GetNext is called a second time // after *end_of_sequence == true if (cache_) { - mutex_lock l2(dataset()->mu_); + mutex_lock l(dataset()->mu_); DCHECK(dataset()->writer_iterator_created_); DCHECK(!dataset()->cache_); cache_.swap(dataset()->cache_); diff --git a/tensorflow/python/data/kernel_tests/cache_dataset_op_test.py b/tensorflow/python/data/kernel_tests/cache_dataset_op_test.py index 02720a2e98..25269dc810 100644 --- a/tensorflow/python/data/kernel_tests/cache_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/cache_dataset_op_test.py @@ -297,6 +297,21 @@ class MemoryCacheDatasetTest(test.TestCase): with self.assertRaises(errors.OutOfRangeError): sess.run(i2.get_next()) + def testCacheTakeRepeat(self): + dataset = dataset_ops.Dataset.range(10).cache().take(5).repeat(2) + itr = dataset.make_one_shot_iterator() + n = itr.get_next() + + expected_values = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + + with self.test_session() as sess: + for i, expected in enumerate(expected_values): + self.assertEqual(expected, sess.run(n), + "Unexpected value at index %s" % i) + + with self.assertRaises(errors.OutOfRangeError): + sess.run(itr.get_next()) + if __name__ == "__main__": test.main() -- GitLab From 61aa925ebaa69b9526cc67384fcde3fa42c9e6f1 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Wed, 21 Mar 2018 18:22:36 -0700 Subject: [PATCH 055/906] Moves TFE_Executor to common_runtime PiperOrigin-RevId: 190001737 --- tensorflow/c/eager/BUILD | 2 + tensorflow/c/eager/c_api.cc | 179 ++++++---------- tensorflow/c/eager/c_api_internal.h | 84 +------- tensorflow/core/common_runtime/eager/BUILD | 22 ++ .../core/common_runtime/eager/context.cc | 142 +++++++++++++ .../core/common_runtime/eager/context.h | 193 ++++++++++++++++++ 6 files changed, 428 insertions(+), 194 deletions(-) create mode 100644 tensorflow/core/common_runtime/eager/context.cc create mode 100644 tensorflow/core/common_runtime/eager/context.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index 841ff48a38..bea5a121b3 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -28,6 +28,7 @@ tf_cuda_library( "//tensorflow/c:c_api", "//tensorflow/c:c_api_internal", "//tensorflow/core:core_cpu", + "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", "//tensorflow/core:core_cpu_internal", @@ -64,6 +65,7 @@ tf_cuda_library( "//tensorflow/core:framework_lite", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", ], diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index a23015c99e..5d668848ab 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -71,18 +71,6 @@ std::atomic_int_fast64_t func_id_generator(0); } // namespace -TFE_ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy) { - if (!soft_placement) { - return original_policy; - } - if (original_policy == TFE_DEVICE_PLACEMENT_EXPLICIT || - original_policy == TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32) { - return TFE_DEVICE_PLACEMENT_SILENT; - } - return original_policy; -} - extern "C" { TFE_ContextOptions* TFE_NewContextOptions() { return new TFE_ContextOptions; } @@ -104,19 +92,7 @@ void TFE_ContextOptionsSetDevicePlacementPolicy( TF_CAPI_EXPORT extern void TFE_ContextSetAsyncForThread(TFE_Context* ctx, unsigned char async, TF_Status* status) { - { - tensorflow::mutex_lock l(ctx->async_map_mu); - ctx->thread_local_async[std::this_thread::get_id()] = async; - } - if (async) { - ctx->executor.EnableAsync(); - } else { - // TODO(agarwal): Currently we add a wait here to handle cases where a sync - // op has a control dependency on an async op, and the latter has not - // executed yet. This wait can be removed by storing all the control inputs - // and waiting for them when executing ops. - status->status = ctx->executor.WaitForAllPendingNodes(); - } + status->status = ctx->context.SetAsyncForThread(async); } void TFE_DeleteContextOptions(TFE_ContextOptions* options) { delete options; } @@ -133,34 +109,26 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) { new tensorflow::DeviceMgr(devices)); tensorflow::Rendezvous* r = new tensorflow::IntraProcessRendezvous(device_mgr.get()); - return new TFE_Context(*opts, std::move(device_mgr), r); + return new TFE_Context(opts->session_options.options, opts->policy, + opts->async, std::move(device_mgr), r); } void TFE_DeleteContext(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.WaitForAllPendingNodes(); - { - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); - } - ctx->rendezvous->Unref(); delete ctx; } TF_DeviceList* TFE_ContextListDevices(TFE_Context* ctx, TF_Status* status) { TF_DeviceList* list = new TF_DeviceList; - ctx->device_manager->ListDeviceAttributes(&list->response); + ctx->context.device_mgr()->ListDeviceAttributes(&list->response); return list; } -void TFE_ContextClearCaches(TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); -} +void TFE_ContextClearCaches(TFE_Context* ctx) { ctx->context.ClearCaches(); } void TFE_ContextSetThreadLocalDevicePlacementPolicy( TFE_Context* ctx, TFE_ContextDevicePlacementPolicy policy) { - tensorflow::mutex_lock ml(ctx->policy_map_mu); - ctx->thread_local_policies[std::this_thread::get_id()] = policy; + ctx->context.SetThreadLocalDevicePlacementPolicy( + static_cast(policy)); } // Note: this function looks up a thread local policy. So it should be called in @@ -168,25 +136,20 @@ void TFE_ContextSetThreadLocalDevicePlacementPolicy( // safe to call this function from the async EagerExecutor threads. extern TFE_ContextDevicePlacementPolicy TFE_ContextGetDevicePlacementPolicy( TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->policy_map_mu); - auto policy_map_it = - ctx->thread_local_policies.find(std::this_thread::get_id()); - if (policy_map_it != ctx->thread_local_policies.end()) { - return policy_map_it->second; - } - return ctx->policy; + return static_cast( + ctx->context.GetDevicePlacementPolicy()); } void TFE_ContextAsyncWait(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.WaitForAllPendingNodes(); + status->status = ctx->context.AsyncWait(); } void TFE_ContextGetStatus(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); } void TFE_ContextAsyncClearError(TFE_Context* ctx) { - ctx->executor.ClearError(); + ctx->context.ClearAsyncError(); } TFE_TensorHandle* TFE_NewTensorHandle(TF_Tensor* t, TF_Status* status) { @@ -259,7 +222,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, // nullptr. tensorflow::Device* src_opd = nullptr; TF_RETURN_IF_ERROR(h->TensorAndDevice(&src, &srcd, &src_opd)); - if (srcd == nullptr) srcd = ctx->devices[0]; + if (srcd == nullptr) srcd = ctx->context.HostCPU(); bool is_same_device = (srcd == dstd) || (DeviceName(srcd) == DeviceName(dstd)); const bool dst_cpu = IsCPU(dstd); @@ -332,8 +295,7 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, status->status = tensorflow::AttrTypeMapForOp(name, &types); if (status->status.ok()) return new TFE_Op(ctx, name, types); if (TF_GetCode(status) == TF_NOT_FOUND) { - tensorflow::mutex_lock l(ctx->functions_mu); - if (ctx->func_lib_def.Find(name) != nullptr) { + if (ctx->context.FindFunctionByName(name)) { status->status = tensorflow::Status::OK(); return new TFE_Op(ctx, name, nullptr); } @@ -346,20 +308,14 @@ void TFE_DeleteOp(TFE_Op* op) { delete op; } void TFE_OpSetDevice(TFE_Op* op, const char* device_name, TF_Status* status) { tensorflow::Device* d = nullptr; if (device_name != nullptr && strlen(device_name) > 0) { - auto it = op->ctx->devices_map.find(device_name); - if (it == op->ctx->devices_map.end()) { - status->status = - tensorflow::errors::InvalidArgument(device_name, " unknown device."); - return; - } - d = it->second; + status->status = op->ctx->context.FindDeviceByName(device_name, &d); } op->device = d; } const char* TFE_OpGetDevice(TFE_Op* op, TF_Status* status) { tensorflow::Device* device = - (op->device == nullptr) ? op->ctx->devices[0] : op->device; + (op->device == nullptr) ? op->ctx->context.HostCPU() : op->device; return device->name().c_str(); } @@ -634,7 +590,7 @@ tensorflow::Status ValidateInputTypeAndPlacement( tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, TFE_Context* ctx, TF_Status* status) { tensorflow::DeviceSet ds; - for (tensorflow::Device* d : ctx->devices) { + for (tensorflow::Device* d : *ctx->context.devices()) { ds.AddDevice(d); } tensorflow::DeviceTypeVector final_devices; @@ -648,7 +604,7 @@ tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, "Could not find valid device for node ", ndef.DebugString()); return nullptr; } - for (tensorflow::Device* d : ctx->devices) { + for (tensorflow::Device* d : *ctx->context.devices()) { if (d->device_type() == final_devices[0].type_string()) { return d; } @@ -663,9 +619,8 @@ tensorflow::Status Execute( const tensorflow::gtl::InlinedVector& op_inputs, tensorflow::KernelAndDevice* kernel, tensorflow::NodeExecStats* maybe_stats, TFE_TensorHandle** retvals, int num_retvals) { - if (!ctx->soft_placement && device == nullptr) { - // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU - device = ctx->devices[0]; + if (!ctx->context.SoftPlacement() && device == nullptr) { + device = ctx->context.HostCPU(); } if (device == nullptr) { @@ -697,18 +652,18 @@ tensorflow::Status Execute( if (maybe_stats != nullptr) { maybe_stats->set_op_end_rel_micros(tensorflow::Env::Default()->NowMicros() - maybe_stats->all_start_micros()); - tensorflow::mutex_lock ml(ctx->metadata_mu); - if (ctx->should_store_metadata.load()) { - auto* step_stats = ctx->run_metadata.mutable_step_stats(); + tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); + if (ctx->context.ShouldStoreMetadata()) { + auto* step_stats = ctx->context.RunMetadataProto()->mutable_step_stats(); // Lazily initialize the RunMetadata with information about all devices if // this is the first call. - while (step_stats->dev_stats_size() < ctx->devices.size()) { + while (step_stats->dev_stats_size() < ctx->context.devices()->size()) { step_stats->add_dev_stats(); } // Find the current device's index. int device_idx = 0; - for (int i = 0; i < ctx->devices.size(); ++i) { - if (ctx->devices[i] == device) { + for (int i = 0; i < ctx->context.devices()->size(); ++i) { + if (ctx->context.devices()->at(i) == device) { device_idx = i; break; } @@ -744,7 +699,7 @@ class ExecuteNode : public tensorflow::EagerNode { tensorflow::NodeExecStats* maybe_stats, const tensorflow::DataTypeVector& output_dtypes, TFE_TensorHandle** retvals, int num_retvals) - : tensorflow::EagerNode(op->ctx->executor.NextId()), + : tensorflow::EagerNode(op->ctx->context.NextId()), ctx_(op->ctx), op_device_(op->device), inputs_(op->inputs), @@ -800,7 +755,7 @@ class CopyToDeviceNode : public tensorflow::EagerNode { public: CopyToDeviceNode(TFE_TensorHandle* src, tensorflow::Device* dstd, TFE_Context* ctx) - : tensorflow::EagerNode(ctx->executor.NextId()), + : tensorflow::EagerNode(ctx->context.NextId()), src_(src), dstd_(dstd), ctx_(ctx), @@ -1063,7 +1018,7 @@ extern "C" { void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, TF_Status* status) { TFE_Context* ctx = op->ctx; - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); if (!status->status.ok()) { return; } @@ -1087,7 +1042,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (op->inputs[i]->dtype == tensorflow::DT_RESOURCE && input_op_device != op->device) { tensorflow::Device* d = - input_op_device == nullptr ? ctx->devices[0] : input_op_device; + input_op_device == nullptr ? ctx->context.HostCPU() : input_op_device; VLOG(1) << "Changing device of operation " << op->name << " to " << d->name() << " because input #" << i << " is a resource in this device."; @@ -1095,40 +1050,35 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, } } tensorflow::Device* device = op->device; - if (!ctx->soft_placement && device == nullptr) { - // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU - device = ctx->devices[0]; + if (!ctx->context.SoftPlacement() && device == nullptr) { + device = ctx->context.HostCPU(); } tensorflow::Fprint128 cache_key = op->attrs.CacheKey(device == nullptr ? "unspecified" : device->name()); - tensorflow::KernelAndDevice* kernel; - { - tensorflow::tf_shared_lock l(ctx->cache_mu); - kernel = tensorflow::gtl::FindPtrOrNull(ctx->kernel_cache, cache_key); - } + tensorflow::KernelAndDevice* kernel = ctx->context.GetCachedKernel(cache_key); if (kernel == nullptr) { const tensorflow::NodeDef& ndef = op->attrs.BuildNodeDef(); - if (ctx->soft_placement && device == nullptr) { + if (ctx->context.SoftPlacement() && device == nullptr) { device = SelectDevice(ndef, ctx, status); if (!status->status.ok()) { return; } } CHECK(device != nullptr); - if (ctx->log_device_placement) { + if (ctx->context.LogDevicePlacement()) { LOG(INFO) << "Executing op " << ndef.op() << " in device " << device->name(); } - kernel = new tensorflow::KernelAndDevice(ctx->rendezvous); + kernel = new tensorflow::KernelAndDevice(ctx->context.GetRendezvous()); // Knowledge of the implementation of Init (and in-turn // FunctionLibraryRuntime::CreateKernel) tells us that ctx->func_lib_def // will be accessed, so grab on to the lock. // See WARNING comment in Execute (before kernel->Run) - would be nice to // rework to avoid this subtlety. - tensorflow::tf_shared_lock l(ctx->functions_mu); - status->status = - tensorflow::KernelAndDevice::Init(ndef, ctx->func_lib(device), kernel); + tensorflow::tf_shared_lock l(*ctx->context.FunctionsMu()); + status->status = tensorflow::KernelAndDevice::Init( + ndef, ctx->context.func_lib(device), kernel); if (!status->status.ok()) { delete kernel; return; @@ -1136,7 +1086,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // Update output_dtypes inside `kernel`. const tensorflow::OpDef* op_def = nullptr; const tensorflow::FunctionDef* function_def = - ctx->func_lib_def.Find(ndef.op()); + ctx->context.FuncLibDef()->Find(ndef.op()); if (function_def != nullptr) { op_def = &(function_def->signature()); } @@ -1152,8 +1102,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (!status->status.ok()) { return; } - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::InsertOrUpdate(&(ctx->kernel_cache), cache_key, kernel); + ctx->context.AddKernelToCache(cache_key, kernel); } const tensorflow::DataTypeVector& output_dtypes = kernel->output_dtypes(); const int output_dtypes_size = output_dtypes.size(); @@ -1171,11 +1120,11 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // device from the one requested above. device = kernel->device(); } - status->status = ValidateInputTypeAndPlacement(ctx, ctx->devices[0], device, - op, kernel->kernel()); + status->status = ValidateInputTypeAndPlacement(ctx, ctx->context.HostCPU(), + device, op, kernel->kernel()); if (!status->status.ok()) return; std::unique_ptr maybe_stats; - if (ctx->should_store_metadata.load()) { + if (ctx->context.ShouldStoreMetadata()) { maybe_stats.reset(new tensorflow::NodeExecStats); maybe_stats->set_node_name(op->name); maybe_stats->set_all_start_micros(tensorflow::Env::Default()->NowMicros()); @@ -1183,14 +1132,14 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, maybe_stats->set_scheduled_micros(tensorflow::Env::Default()->NowMicros()); // TODO(apassos) track referenced tensors } - if (ctx->Async()) { + if (ctx->context.Async()) { // Note that for async mode, execution order will make sure that all // input handles are ready before executing them. // TODO(agarwal): Consider executing "cheap" kernels inline for performance. tensorflow::EagerNode* node = new ExecuteNode(op, kernel, maybe_stats.release(), output_dtypes, retvals, *num_retvals); - ctx->executor.Add(node); + ctx->context.ExecutorAdd(node); } else { // Execute checks if retvals[i] is nullptr or not to figure if it needs to // allocate it. @@ -1206,23 +1155,24 @@ TFE_TensorHandle* TFE_TensorHandleCopyToDevice(TFE_TensorHandle* h, TFE_Context* ctx, const char* device_name, TF_Status* status) { - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); if (!status->status.ok()) { return nullptr; } - tensorflow::Device* dstd = ctx->devices[0]; + tensorflow::Device* dstd = ctx->context.HostCPU(); if (device_name != nullptr && strlen(device_name) > 0) { - status->status = ctx->device_manager->LookupDevice(device_name, &dstd); + status->status = + ctx->context.device_mgr()->LookupDevice(device_name, &dstd); if (!status->status.ok()) return nullptr; } - if (ctx->Async()) { + if (ctx->context.Async()) { // Note that `h` may not be currently ready. However execution order will // make sure that `h` is ready before the copy is actually done. CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); TFE_TensorHandle* output = node->dst(); // Note that calling Add makes `node` accessible by the EagerExecutor // thread. So further accesses need to be thread-safe. - ctx->executor.Add(node); + ctx->context.ExecutorAdd(node); return output; } else { TFE_TensorHandle* output = nullptr; @@ -1240,24 +1190,20 @@ void TFE_ContextAddFunctionDef(TFE_Context* ctx, tensorflow::errors::InvalidArgument("Invalid FunctionDef proto"); return; } - tensorflow::mutex_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.AddFunctionDef(function_def); + status->status = ctx->context.AddFunctionDef(function_def); } void TFE_ContextAddFunction(TFE_Context* ctx, TF_Function* function, TF_Status* status) { - tensorflow::mutex_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.AddFunctionDef(function->fdef); + status->status = ctx->context.AddFunctionDef(function->fdef); } void TFE_ContextEnableRunMetadata(TFE_Context* ctx) { - ctx->should_store_metadata.store(true); + ctx->context.SetShouldStoreMetadata(true); } void TFE_ContextDisableRunMetadata(TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->metadata_mu); - ctx->should_store_metadata.store(false); - ctx->run_metadata.Clear(); + ctx->context.SetShouldStoreMetadata(false); } } // extern "C" @@ -1286,9 +1232,9 @@ void TFE_ContextExportRunMetadata(TFE_Context* ctx, TF_Buffer* buf, TF_Status* status) { TFE_ContextAsyncWait(ctx, status); if (!status->status.ok()) return; - tensorflow::mutex_lock ml(ctx->metadata_mu); - status->status = MessageToBuffer(ctx->run_metadata, buf); - ctx->run_metadata.Clear(); + tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); + status->status = MessageToBuffer(*ctx->context.RunMetadataProto(), buf); + ctx->context.RunMetadataProto()->Clear(); } namespace { @@ -1363,11 +1309,6 @@ void SetOpAttrValueScalar(TFE_Context* ctx, TFE_Op* op, } // namespace tensorflow -bool TFE_Context::Async() const { - tensorflow::mutex_lock l(async_map_mu); - return tensorflow::gtl::FindWithDefault( - thread_local_async, std::this_thread::get_id(), async_default); -} bool TFE_TensorHandle::IsReady() { if (node_id == 0) return true; @@ -1381,7 +1322,7 @@ tensorflow::Status TFE_TensorHandle::WaitReady() { { tensorflow::mutex_lock l(ctx_mutex_); if (ctx_ == nullptr) return tensorflow::Status::OK(); - executor = &ctx_->executor; + executor = ctx_->context.Executor(); } return executor->WaitFor(node_id); } diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index a79f8ddd33..5b29120b40 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/c/c_api_internal.h" #include "tensorflow/c/eager/runtime.h" #include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/context.h" #include "tensorflow/core/common_runtime/eager/eager_executor.h" #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" #include "tensorflow/core/common_runtime/function.h" @@ -52,85 +53,18 @@ struct TFE_ContextOptions { TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32}; }; -TFE_ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy); - struct TFE_Context { - explicit TFE_Context(const TFE_ContextOptions& opts, + explicit TFE_Context(const tensorflow::SessionOptions& opts, + TFE_ContextDevicePlacementPolicy default_policy, + bool async, std::unique_ptr device_mgr, tensorflow::Rendezvous* rendezvous) - : soft_placement( - opts.session_options.options.config.allow_soft_placement()), - policy(PlacementPolicy(soft_placement, opts.policy)), - device_manager(std::move(device_mgr)), - devices(device_manager->ListDevices()), - rendezvous(rendezvous), - pflr(new tensorflow::ProcessFunctionLibraryRuntime( - device_manager.get(), opts.session_options.options.env, - TF_GRAPH_DEF_VERSION, &func_lib_def, {})), - log_device_placement( - opts.session_options.options.config.log_device_placement()), - async_default(opts.async) { - if (async_default) executor.EnableAsync(); - - for (auto* device : devices) { - devices_map[tensorflow::StringPiece(device->name())] = device; - } - } - - const bool soft_placement; - const TFE_ContextDevicePlacementPolicy policy; - - // Note: we cannot use C++11 thread_local here as there is no concept of a - // thread-local-object-local variable in C++11. - tensorflow::mutex policy_map_mu; - std::unordered_map - thread_local_policies GUARDED_BY(policy_map_mu); - - std::unique_ptr device_manager; - // Devices owned by device_manager - std::vector devices; - // All devices are not owned. - tensorflow::gtl::FlatMap - devices_map; - tensorflow::Rendezvous* const rendezvous; - - tensorflow::mutex functions_mu; - tensorflow::FunctionLibraryDefinition func_lib_def GUARDED_BY(functions_mu){ - tensorflow::OpRegistry::Global(), {}}; - - // One FunctionLibraryRuntime per device. - // func_libs[i] is the FunctionLibraryRuntime corresponding to - // session->devices[i]. - const std::unique_ptr pflr; - - tensorflow::mutex cache_mu; - std::unordered_map - kernel_cache GUARDED_BY(cache_mu); - - tensorflow::FunctionLibraryRuntime* func_lib(tensorflow::Device* d) const { - return pflr->GetFLR(d->name()); - } + : context(opts, + static_cast( + default_policy), + async, std::move(device_mgr), rendezvous) {} - // Whether we should compute RunMetadata. - std::atomic should_store_metadata{false}; - tensorflow::mutex metadata_mu; - tensorflow::RunMetadata run_metadata GUARDED_BY(metadata_mu); - const bool log_device_placement; - // EagerExecutor for async execution. - tensorflow::EagerExecutor executor; - - // True if running in asynchronous mode. - bool Async() const; - - // True if the default value for execution mode is async. Note that this value - // can be overridden per thread based on `thread_local_async` overrides. - const bool async_default; - mutable tensorflow::mutex async_map_mu; - std::unordered_map thread_local_async - GUARDED_BY(async_map_mu); + tensorflow::EagerContext context; }; struct TFE_TensorHandle : public tensorflow::core::RefCounted { diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index 8ba560bef8..de10b10b7e 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -32,6 +32,28 @@ tf_cuda_library( ], ) +tf_cuda_library( + name = "context", + srcs = [ + "context.cc", + ], + hdrs = [ + "context.h", + ], + visibility = ["//tensorflow:internal"], + deps = [ + ":eager_executor", + ":kernel_and_device", + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", + ], +) + tf_cuda_library( name = "kernel_and_device", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc new file mode 100644 index 0000000000..5e8d083cd2 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/context.cc @@ -0,0 +1,142 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/common_runtime/eager/context.h" + +namespace tensorflow { + +ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, ContextDevicePlacementPolicy original_policy) { + if (!soft_placement) { + return original_policy; + } + if (original_policy == DEVICE_PLACEMENT_EXPLICIT || + original_policy == DEVICE_PLACEMENT_SILENT_FOR_INT32) { + return DEVICE_PLACEMENT_SILENT; + } + return original_policy; +} + +EagerContext::EagerContext(const SessionOptions& opts, + ContextDevicePlacementPolicy default_policy, + bool async, std::unique_ptr device_mgr, + Rendezvous* rendezvous) + : soft_placement_(opts.config.allow_soft_placement()), + policy_(PlacementPolicy(soft_placement_, default_policy)), + device_manager_(std::move(device_mgr)), + devices_(device_manager_->ListDevices()), + rendezvous_(rendezvous), + pflr_(new ProcessFunctionLibraryRuntime(device_manager_.get(), opts.env, + TF_GRAPH_DEF_VERSION, + &func_lib_def_, {})), + log_device_placement_(opts.config.log_device_placement()), + async_default_(async) { + if (async_default_) { + executor_.EnableAsync(); + } + + for (auto* device : devices_) { + devices_map_[device->name()] = device; + } +} + +bool EagerContext::Async() const { + mutex_lock l(async_map_mu_); + return gtl::FindWithDefault(thread_local_async_, std::this_thread::get_id(), + async_default_); +} + +Status EagerContext::SetAsyncForThread(bool async) { + { + tensorflow::mutex_lock l(async_map_mu_); + thread_local_async_[std::this_thread::get_id()] = async; + } + if (async) { + executor_.EnableAsync(); + } else { + // TODO(agarwal): Currently we add a wait here to handle cases where a + // sync op has a control dependency on an async op, and the latter has not + // executed yet. This wait can be removed by storing all the control + // inputs and waiting for them when executing ops. + return executor_.WaitForAllPendingNodes(); + } + return Status::OK(); +} + +void EagerContext::ClearCaches() { + mutex_lock ml(cache_mu_); + gtl::STLDeleteValues(&kernel_cache_); +} + +void EagerContext::SetThreadLocalDevicePlacementPolicy( + ContextDevicePlacementPolicy policy) { + mutex_lock ml(policy_map_mu_); + thread_local_policies_[std::this_thread::get_id()] = policy; +} + +ContextDevicePlacementPolicy EagerContext::GetDevicePlacementPolicy() { + mutex_lock ml(policy_map_mu_); + auto policy_map_it = thread_local_policies_.find(std::this_thread::get_id()); + if (policy_map_it != thread_local_policies_.end()) { + return policy_map_it->second; + } + return policy_; +} + +EagerContext::~EagerContext() { + executor_.WaitForAllPendingNodes().IgnoreError(); + ClearCaches(); + rendezvous_->Unref(); +} + +bool EagerContext::FindFunctionByName(const string& name) { + mutex_lock l(functions_mu_); + return func_lib_def_.Find(name) != nullptr; +} + +Status EagerContext::FindDeviceByName(const string& name, Device** result) { + auto it = devices_map_.find(name); + if (it == devices_map_.end()) { + return errors::InvalidArgument(name, " unknown device."); + } + *result = it->second; + return Status::OK(); +} + +Status EagerContext::AddFunctionDef(const FunctionDef& fdef) { + mutex_lock l(functions_mu_); + return func_lib_def_.AddFunctionDef(fdef); +} + +KernelAndDevice* EagerContext::GetCachedKernel(Fprint128 cache_key) { + tf_shared_lock l(cache_mu_); + return gtl::FindPtrOrNull(kernel_cache_, cache_key); +} + +void EagerContext::AddKernelToCache(Fprint128 cache_key, + KernelAndDevice* kernel) { + mutex_lock ml(cache_mu_); + gtl::InsertOrUpdate(&kernel_cache_, cache_key, kernel); +} + +void EagerContext::SetShouldStoreMetadata(bool value) { + should_store_metadata_.store(value); + if (!value) { + mutex_lock ml(metadata_mu_); + run_metadata_.Clear(); + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h new file mode 100644 index 0000000000..d525d44fe4 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/context.h @@ -0,0 +1,193 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/eager_executor.h" +#include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/framework/rendezvous.h" +#include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/lib/gtl/stl_util.h" +#include "tensorflow/core/platform/fingerprint.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/platform/thread_annotations.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" + +namespace tensorflow { + +// Note: there's a copy enum in eager/c_api.h. It should be kept in sync. +enum ContextDevicePlacementPolicy { + // Running operations with input tensors on the wrong device will fail. When + // soft placement is enabled acts like TFE_DEVICE_PLACEMENT_SILENT. + DEVICE_PLACEMENT_EXPLICIT = 0, + // Copy the tensor to the right device but log a warning. + DEVICE_PLACEMENT_WARN = 1, + // Silently copy the tensor, which has a performance cost since the + // operation will be blocked till the copy completes. + DEVICE_PLACEMENT_SILENT = 2, + // Default placement policy which silently copies int32 tensors but not other + // dtypes. When soft placement is enabled acts like + // TFE_DEVICE_PLACEMENT_SILENT. + DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, +}; + +ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, ContextDevicePlacementPolicy original_policy); + +class EagerContext { + public: + explicit EagerContext(const SessionOptions& opts, + ContextDevicePlacementPolicy default_policy, bool async, + std::unique_ptr device_mgr, + Rendezvous* rendezvous); + + ~EagerContext(); + + // Returns the function library runtime for the given device. + FunctionLibraryRuntime* func_lib(Device* d) const { + return pflr_->GetFLR(d->name()); + } + + // True if running in asynchronous mode. + bool Async() const; + + EagerExecutor* Executor() { return &executor_; } + + // Sets whether this thread should run in synchronous or asynchronous mode. + Status SetAsyncForThread(bool async); + + // TODO(apassos) make this return a constant reference + gtl::FlatMap* device_map() { + return &devices_map_; + } + + // TODO(apassos) make this return a constant reference + std::vector* devices() { return &devices_; } + + // Clears the kernel caches. + void ClearCaches(); + + // Sets the device placement policy for the current thread. + void SetThreadLocalDevicePlacementPolicy(ContextDevicePlacementPolicy policy); + + // Returns the device placement policy for the current thread. + ContextDevicePlacementPolicy GetDevicePlacementPolicy(); + + Status AsyncWait() { return executor_.WaitForAllPendingNodes(); } + + Status GetStatus() { return executor_.status(); } + + void ClearAsyncError() { executor_.ClearError(); } + + bool FindFunctionByName(const string& name); + + Status FindDeviceByName(const string& name, Device** result); + + Device* HostCPU() { return devices_[0]; } + + bool SoftPlacement() { return soft_placement_; } + + uint64 NextId() { return executor_.NextId(); } + + void ExecutorAdd(EagerNode* node) { executor_.Add(node); } + + Status AddFunctionDef(const FunctionDef& fdef); + + KernelAndDevice* GetCachedKernel(Fprint128 cache_key); + + void AddKernelToCache(Fprint128 cache_key, KernelAndDevice* kernel); + + bool LogDevicePlacement() { return log_device_placement_; } + + Rendezvous* GetRendezvous() { return rendezvous_; } + + mutex* FunctionsMu() { return &functions_mu_; } + + tensorflow::DeviceMgr* device_mgr() { return device_manager_.get(); } + + // TODO(apassos) remove the need for this + void ReleaseDeviceMgr() { device_manager_.release(); } + + // TODO(apassos) clean up RunMetadata storage. + mutex* MetadataMu() { return &metadata_mu_; } + bool ShouldStoreMetadata() { return should_store_metadata_.load(); } + void SetShouldStoreMetadata(bool value); + RunMetadata* RunMetadataProto() { return &run_metadata_; } + + FunctionLibraryDefinition* FuncLibDef() { return &func_lib_def_; } + + private: + const bool soft_placement_; + const ContextDevicePlacementPolicy policy_; + + // Note: we cannot use C++11 thread_local here as there is no concept of a + // thread-local-object-local variable in C++11. + mutex policy_map_mu_; + std::unordered_map + thread_local_policies_ GUARDED_BY(policy_map_mu_); + + std::unique_ptr device_manager_; + // Devices owned by device_manager + std::vector devices_; + // All devices are not owned. + gtl::FlatMap devices_map_; + Rendezvous* const rendezvous_; + + mutex functions_mu_; + FunctionLibraryDefinition func_lib_def_ GUARDED_BY(functions_mu_){ + OpRegistry::Global(), {}}; + + // One FunctionLibraryRuntime per device. + // func_libs[i] is the FunctionLibraryRuntime corresponding to + // session->devices[i]. + const std::unique_ptr pflr_; + + mutex cache_mu_; + std::unordered_map kernel_cache_ + GUARDED_BY(cache_mu_); + + // Whether we should compute RunMetadata. + std::atomic should_store_metadata_{false}; + mutex metadata_mu_; + RunMetadata run_metadata_ GUARDED_BY(metadata_mu_); + const bool log_device_placement_; + // EagerExecutor for async execution. + EagerExecutor executor_; + + // True if the default value for execution mode is async. Note that this value + // can be overridden per thread based on `thread_local_async` overrides. + const bool async_default_; + mutable mutex async_map_mu_; + std::unordered_map thread_local_async_ + GUARDED_BY(async_map_mu_); +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ -- GitLab From 73bd57d80111dc957d117b6ae98bc2354f766604 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 21 Mar 2018 19:12:18 -0700 Subject: [PATCH 056/906] Add tensor quantization info to python wrapper PiperOrigin-RevId: 190005998 --- tensorflow/contrib/lite/python/interpreter.py | 2 ++ .../contrib/lite/python/interpreter_test.py | 4 ++++ .../interpreter_wrapper/interpreter_wrapper.cc | 17 +++++++++++++++++ .../interpreter_wrapper/interpreter_wrapper.h | 1 + 4 files changed, 24 insertions(+) diff --git a/tensorflow/contrib/lite/python/interpreter.py b/tensorflow/contrib/lite/python/interpreter.py index accdd04671..b8638007f7 100644 --- a/tensorflow/contrib/lite/python/interpreter.py +++ b/tensorflow/contrib/lite/python/interpreter.py @@ -71,6 +71,7 @@ class Interpreter(object): tensor_name = self._interpreter.TensorName(tensor_index) tensor_size = self._interpreter.TensorSize(tensor_index) tensor_type = self._interpreter.TensorType(tensor_index) + tensor_quantization = self._interpreter.TensorQuantization(tensor_index) if not tensor_name or not tensor_type: raise ValueError('Could not get tensor details') @@ -80,6 +81,7 @@ class Interpreter(object): 'index': tensor_index, 'shape': tensor_size, 'dtype': tensor_type, + 'quantization': tensor_quantization, } return details diff --git a/tensorflow/contrib/lite/python/interpreter_test.py b/tensorflow/contrib/lite/python/interpreter_test.py index e85390c56c..bf124410f3 100644 --- a/tensorflow/contrib/lite/python/interpreter_test.py +++ b/tensorflow/contrib/lite/python/interpreter_test.py @@ -39,12 +39,14 @@ class InterpreterTest(test_util.TensorFlowTestCase): self.assertEqual('input', input_details[0]['name']) self.assertEqual(np.float32, input_details[0]['dtype']) self.assertTrue(([1, 4] == input_details[0]['shape']).all()) + self.assertEqual((0.0, 0), input_details[0]['quantization']) output_details = interpreter.get_output_details() self.assertEqual(1, len(output_details)) self.assertEqual('output', output_details[0]['name']) self.assertEqual(np.float32, output_details[0]['dtype']) self.assertTrue(([1, 4] == output_details[0]['shape']).all()) + self.assertEqual((0.0, 0), output_details[0]['quantization']) test_input = np.array([[1.0, 2.0, 3.0, 4.0]], dtype=np.float32) expected_output = np.array([[4.0, 3.0, 2.0, 1.0]], dtype=np.float32) @@ -67,12 +69,14 @@ class InterpreterTest(test_util.TensorFlowTestCase): self.assertEqual('input', input_details[0]['name']) self.assertEqual(np.uint8, input_details[0]['dtype']) self.assertTrue(([1, 4] == input_details[0]['shape']).all()) + self.assertEqual((1.0, 0), input_details[0]['quantization']) output_details = interpreter.get_output_details() self.assertEqual(1, len(output_details)) self.assertEqual('output', output_details[0]['name']) self.assertEqual(np.uint8, output_details[0]['dtype']) self.assertTrue(([1, 4] == output_details[0]['shape']).all()) + self.assertEqual((1.0, 0), output_details[0]['quantization']) test_input = np.array([[1, 2, 3, 4]], dtype=np.uint8) expected_output = np.array([[4, 3, 2, 1]], dtype=np.uint8) diff --git a/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.cc b/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.cc index 14e1190c80..35ad226b78 100644 --- a/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.cc +++ b/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.cc @@ -109,6 +109,13 @@ PyObject* PyArrayFromIntVector(const int* data, npy_intp size) { return PyArray_SimpleNewFromData(1, &size, NPY_INT32, pydata); } +PyObject* PyTupleFromQuantizationParam(const TfLiteQuantizationParams& param) { + PyObject* result = PyTuple_New(2); + PyTuple_SET_ITEM(result, 0, PyFloat_FromDouble(param.scale)); + PyTuple_SET_ITEM(result, 1, PyInt_FromLong(param.zero_point)); + return result; +} + } // namespace InterpreterWrapper::InterpreterWrapper( @@ -214,6 +221,16 @@ PyObject* InterpreterWrapper::TensorSize(int i) const { return PyArray_Return(reinterpret_cast(np_array)); } +PyObject* InterpreterWrapper::TensorQuantization(int i) const { + if (!interpreter_ || i >= interpreter_->tensors_size() || i < 0) { + Py_INCREF(Py_None); + return Py_None; + } + + const TfLiteTensor* tensor = interpreter_->tensor(i); + return PyTupleFromQuantizationParam(tensor->params); +} + bool InterpreterWrapper::SetTensor(int i, PyObject* value) { if (!interpreter_) { LOG(ERROR) << "Invalid interpreter."; diff --git a/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.h b/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.h index 63bdb30f79..0972c57259 100644 --- a/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.h +++ b/tensorflow/contrib/lite/python/interpreter_wrapper/interpreter_wrapper.h @@ -54,6 +54,7 @@ class InterpreterWrapper { std::string TensorName(int i) const; PyObject* TensorType(int i) const; PyObject* TensorSize(int i) const; + PyObject* TensorQuantization(int i) const; bool SetTensor(int i, PyObject* value); PyObject* GetTensor(int i) const; -- GitLab From 212a42a01d7b30fec1d6f8ca34dbf9c095938d4a Mon Sep 17 00:00:00 2001 From: Mingsheng Hong Date: Wed, 21 Mar 2018 22:11:10 -0700 Subject: [PATCH 057/906] Simplified the experimental APIs related to TPU execution, by moving the graph rewrite functionality out of it. PiperOrigin-RevId: 190016936 --- tensorflow/c/BUILD | 1 + tensorflow/c/c_api_experimental.cc | 432 ++--------------------------- tensorflow/c/c_api_experimental.h | 47 ++-- 3 files changed, 39 insertions(+), 441 deletions(-) diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index c178d7f81f..4332f44e5d 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -116,6 +116,7 @@ tf_cuda_library( ":c_api", ":c_api_internal", "//tensorflow/compiler/jit/legacy_flags:mark_for_compilation_pass_flags", + "//tensorflow/contrib/tpu:all_ops", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc index 34b9dec3ee..29caf508e7 100644 --- a/tensorflow/c/c_api_experimental.cc +++ b/tensorflow/c/c_api_experimental.cc @@ -22,389 +22,7 @@ limitations under the License. #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/protobuf/config.pb.h" -using tensorflow::Node; -using tensorflow::NodeBuilder; -using tensorflow::NodeDef; using tensorflow::Status; -using tensorflow::string; - -namespace { - -const char* const DEVICE_TPU_REPLICATED_CORE = "TPU_REPLICATED_CORE"; -const char* const DEVICE_TPU_SYSTEM = "TPU_SYSTEM"; - -TF_Operation* ToTF_Operation(Node* node) { - return static_cast(static_cast(node)); -} - -// Graph rewrite algorithm (modeled after the python TPU graph rewrite path): -// -// 1. For each input node I, with C being the consumer node of I's output: -// -// a) When infeed is not specified, feed I to a new TPUReplicatedInput node -// (both running on CPU), which in turn feeds a new Identity node N, and N feeds -// C (both running on TPU). -// -// b) Otherwise, feed I to a new InfeedEnqueueTuple node IE, both running on -// CPU. Also set an InfeedDequeueTuple node ID to feed C, both running on -// TPU. -// -// In case b), if we have multiple input nodes, they all feed into the same -// InfeedEnqueueTuple node, so that the graph has a single pair of infeed -// enqueue and dequeue nodes. The list of output tensors from the dequeue node -// can go to different consumer nodes. For example, say the original graph has -// input nodes I1 and I2 respectively feeding nodes C1 and C2. After the rewrite -// with infeed ops, we will have: I1 and I2 feed a single infeed enqueue node -// IE, and a corresponding infeed dequeue node ID produces a list of two -// tensors, respectively feeding C1 and C2. -// -// 2. Rewrite all existing graph nodes by adding an attribute on TPU -// cluster. For each node C reading some input node I, rewire it to read from a -// new input node generated in step #1 above. -// -// 3. For each output node O, feed it to a new Identity node, which in turn -// feeds a new TPUReplicatedOutput node, which in turn feeds a new Identity node -// M. Return the set of new output nodes (the "M" nodes) for caller to fetch -// from. -// -// Limitations compared to the python TPU rewrite path: -// - # replicas is always 1. -// - Less error checking. -// -// TODO(hongm): Simplify the graph rewrite to generating fewer TPUReplicate -// related nodes. -class GraphRewriter { - public: - GraphRewriter(TF_Graph* graph, int num_input_nodes, - const TF_Output* input_nodes, int num_output_nodes, - const TF_Output* output_nodes) - EXCLUSIVE_LOCKS_REQUIRED(graph->mu) - : graph_(graph), input_nodes_(input_nodes) { - for (int i = 0; i < num_input_nodes; ++i) { - // Will fill in the value part later when we create the associated new - // input node. - input_node_map_[input_nodes[i].oper->node.name()] = - NodeBuilder::NodeOut(nullptr, -1); - } - - // Grab all existing nodes for the upcoming rewrite, before mutating the - // graph. - for (Node* n : graph->graph.nodes()) { - nodes_to_rewrite_.push_back(n); - } - - for (int i = 0; i < num_output_nodes; ++i) { - output_node_map_.emplace(output_nodes[i].oper->node.name(), - PortIndexPair{output_nodes[i].index, i}); - } - } - - // On success, sets `config_op` and `shutdown_op` to the corresponding - // "ConfigureDistributedTPU" and "ShutdownDistributedTPU" nodes added to the - // graph. - tensorflow::Status Rewrite(TF_Output* new_output_nodes, - TF_Operation** infeed_enqueue_node, - TF_Output* config_op, TF_Output* shutdown_op) - EXCLUSIVE_LOCKS_REQUIRED(graph_->mu) { - TF_RETURN_IF_ERROR(ProcessInputNodes(infeed_enqueue_node)); - - return RewriteGraphAndAddOutputNodes(new_output_nodes, config_op, - shutdown_op); - } - - private: - // Synthesizes new graph nodes (infeed enqueue or TPU replicated input - // nodes) for the input nodes, and creates a replicated metadata node. - // - // When `infeed_enqueue_node` is non-NULL and there are some input nodes, - // also adds the infeed dequeue node. - tensorflow::Status ProcessInputNodes(TF_Operation** infeed_enqueue_node) - EXCLUSIVE_LOCKS_REQUIRED(graph_->mu) { - Node* metadata_node; - TF_RETURN_IF_ERROR( - NodeBuilder(metadata_node_name_.c_str(), "TPUReplicateMetadata") - .Attr("num_replicas", 1) - .Attr("_tpu_replicate", cluster_name_.c_str()) - .Finalize(&graph_->graph, &metadata_node)); - - Node* dequeue_node = nullptr; - // Be deterministic in the corner case where `use_infeed` below is false. - if (infeed_enqueue_node) *infeed_enqueue_node = nullptr; - const bool use_infeed = - infeed_enqueue_node != nullptr && !input_node_map_.empty(); - if (use_infeed) { - std::vector new_input_list; - new_input_list.reserve(input_node_map_.size()); - std::vector input_dtypes; - input_dtypes.reserve(input_node_map_.size()); - std::vector input_shapes; - input_shapes.reserve(input_node_map_.size()); - for (int i = 0; i < input_node_map_.size(); ++i) { - Node& input_node = input_nodes_[i].oper->node; - new_input_list.push_back( - NodeBuilder::NodeOut(&input_node, input_nodes_[i].index)); - input_dtypes.push_back(input_node.output_type(input_nodes_[i].index)); - tensorflow::TensorShapeProto shape; - TF_RETURN_IF_ERROR( - tensorflow::GetNodeAttr(input_node.attrs(), "shape", &shape)); - VLOG(1) << "Input node " << i << " has shape " << shape.DebugString(); - input_shapes.push_back(shape); - } - // Enqueue always runs on CPU. - Node* enqueue_node; - TF_RETURN_IF_ERROR(NodeBuilder("InfeedEnqueueTuple", "InfeedEnqueueTuple") - .Input(new_input_list) - .Device("/device:CPU:0") - .Attr("device_ordinal", 0) - .Attr("dtypes", input_dtypes) - .Attr("shapes", input_shapes) - .Finalize(&graph_->graph, &enqueue_node)); - *infeed_enqueue_node = ToTF_Operation(enqueue_node); - // The dequeue node should be put onto the "_tpu_replicate" cluster. - TF_RETURN_IF_ERROR( - NodeBuilder("TPUReplicate/InfeedDequeueTuple", "InfeedDequeueTuple") - .ControlInput(metadata_node) - .Attr("_tpu_replicate", cluster_name_.c_str()) - .Attr("dtypes", input_dtypes) - .Attr("shapes", input_shapes) - .Finalize(&graph_->graph, &dequeue_node)); - } - - for (int i = 0; i < input_node_map_.size(); ++i) { - VLOG(1) << "Handling input node " << input_nodes_[i].oper->node.name(); - if (use_infeed) { - DCHECK(dequeue_node); - input_node_map_[input_nodes_[i].oper->node.name()] = - NodeBuilder::NodeOut(dequeue_node, i); - } else { - Node* replicated_input_node; - { - std::string replicated_input_name("TPUReplicate/input" + - std::to_string(i)); - NodeBuilder::NodeOut input(&input_nodes_[i].oper->node, - input_nodes_[i].index); - std::vector input_list; - input_list.push_back(input); - TF_RETURN_IF_ERROR( - NodeBuilder(replicated_input_name.c_str(), "TPUReplicatedInput") - // This op requires an input list. - .Input(input_list) - .Finalize(&graph_->graph, &replicated_input_node)); - } - - { - Node* new_input_node; - const std::string new_input_name("TPUReplicate/replicated_input_" + - std::to_string(i)); - TF_RETURN_IF_ERROR(NodeBuilder(new_input_name.c_str(), "Identity") - .Input(replicated_input_node, 0) - .ControlInput(metadata_node) - .Attr("_tpu_replicate", cluster_name_.c_str()) - .Finalize(&graph_->graph, &new_input_node)); - DCHECK_GT(input_node_map_.count(input_nodes_[i].oper->node.name()), - 0); - input_node_map_[input_nodes_[i].oper->node.name()] = - NodeBuilder::NodeOut(new_input_node, 0); - } - } - } - return Status::OK(); - } - - // On success, sets `config_op` and `shutdown_op` to the corresponding - // "ConfigureDistributedTPU" and "ShutdownDistributedTPU" nodes added to the - // graph. - tensorflow::Status RewriteGraphAndAddOutputNodes(TF_Output* new_output_nodes, - TF_Output* config_op, - TF_Output* shutdown_op) - EXCLUSIVE_LOCKS_REQUIRED(graph_->mu) { - tensorflow::Status s; - // For each non-input node in the input graph, place the node in a "TPU - // replicate cluster" via an attribute, and with the above metadata node - // as a control dependency. - // - // Although we have handled the input nodes in ProcessInputNodes(), some - // of those nodes may also serve as output nodes, which we will handle - // below. - for (Node* n : nodes_to_rewrite_) { - if (n->IsSource()) continue; - VLOG(1) << "Rewriting node " << n->name(); - - if (n->IsSink()) { - // TODO(hongm): Rewire SINK to be control dependent on the new input - // nodes created above? - continue; - } - - const NodeDef& old_def = n->def(); - // Let node C be the consumer of `n`'s output in the original graph. - // This new node will feed into C in the rewritten graph. - NodeBuilder::NodeOut new_node; - if (input_node_map_.count(n->name())) { - new_node = input_node_map_[n->name()]; - } else { - // This node is to replace `n` in the graph. - NodeDef new_def = n->def(); - const std::string new_node_name = "TPUReplicate/" + n->name(); - new_def.set_name(new_node_name); - new_def.clear_input(); - for (int i = 0; i < old_def.input_size(); ++i) { - const string old_input_name = old_def.input(i); - // When there are multiple input nodes that get mapped to the same - // infeed dequeue node, use different output ports of the dequeue - // node. e.g. Say in the original graph, input I1 feeds C1, and I2 - // feeds C2. After the rewrite, I1 and I2 both feed a new infeed - // enqueue node, and the corresponding dequeue node has its output - // port 0 feeding C1, and output port 1 feeding C2. Note C1 and C2 - // could be the same node (e.g. an Add that takes 2 inputs). - const string new_input_name = - input_node_map_.count(old_input_name) > 0 - ? tensorflow::strings::StrCat( - input_node_map_[old_input_name].node->name(), ":", - input_node_map_[old_input_name].index) - : "TPUReplicate/" + old_input_name; - new_def.add_input(new_input_name); - } - if (old_def.input_size() == 0) { - // It is sufficient to only set control dependency of nodes without - // input. Other nodes with input(s) with inherit such control - // dependency. - // e.g. say the graph computes add(x, y). Once we make nodes x and y - // control-dependent on the metadata node, node add will inherit - // such control dependency indirectly. - new_def.add_input( - tensorflow::strings::StrCat("^", metadata_node_name_.c_str())); - } - tensorflow::AddNodeAttr("_tpu_replicate", cluster_name_.c_str(), - &new_def); - new_node = NodeBuilder::NodeOut(graph_->graph.AddNode(new_def, &s), 0); - if (!s.ok()) { - return s; - } - VLOG(1) << "The rewritten node node is " - << new_node.node->DebugString(); - } - - if (output_node_map_.count(n->name()) > 0) { - VLOG(1) << "Handling output node " << n->name(); - auto range_it = output_node_map_.equal_range(n->name()); - for (auto it = range_it.first; it != range_it.second; ++it) { - const PortIndexPair& pair = it->second; - Node* out_identity_node; - { - // If this output node is also an input, use the input_node_map_'s - // stored port, which would also work for an infeed dequeue op. - // Otherwise use pair.port. - // An example of the former: Say the graph has input nodes I1 and - // I2, and the output nodes are also I1 and I2. In the rewritten - // graph with infeed, the 2 output nodes will both come from a - // single infeed dequeue node ID, with output ports respectively - // set to 0 and 1. - const int output_port = - input_node_map_.count(n->name()) ? new_node.index : pair.port; - VLOG(1) << "Handling its output port " << output_port - << " at output index " << pair.index; - std::string output_node_name = "TPUReplicate/Identity"; - if (pair.index > 0) { - output_node_name += "_" + std::to_string(pair.index); - } - TF_RETURN_IF_ERROR( - NodeBuilder(output_node_name.c_str(), "Identity") - .Input(new_node.node, output_port) - .Device(!old_def.device().empty() - ? old_def.device() - : tensorflow::strings::StrCat( - "/device:", DEVICE_TPU_REPLICATED_CORE)) - .Attr("_tpu_replicate", cluster_name_.c_str()) - .Finalize(&graph_->graph, &out_identity_node)); - VLOG(1) << "out_identity_node: " - << out_identity_node->DebugString(); - } - - Node* replicated_output_node; - { - const std::string replicated_output_node_name = - "TPUReplicate/output" + std::to_string(pair.index); - TF_RETURN_IF_ERROR( - NodeBuilder(replicated_output_node_name.c_str(), - "TPUReplicatedOutput") - .Input(out_identity_node, 0) - .Attr("num_replicas", 1) - .Finalize(&graph_->graph, &replicated_output_node)); - VLOG(1) << "replicated_output_node: " - << replicated_output_node->DebugString(); - } - - Node* final_output_node; - const std::string final_output_node_name = - "TPUReplicate/output_" + std::to_string(pair.index) + "_shard_" + - std::to_string(0); - TF_RETURN_IF_ERROR( - NodeBuilder(final_output_node_name.c_str(), "Identity") - .Input(replicated_output_node, 0) - .Finalize(&graph_->graph, &final_output_node)); - VLOG(1) << "new_output_node: " << final_output_node->DebugString(); - auto oper = ToTF_Operation(final_output_node); - new_output_nodes[pair.index] = {oper, 0}; - } - } - - if (input_node_map_.count(n->name()) == 0) { - graph_->graph.RemoveNode(n); - } - } - - { - Node* config_node; - TF_RETURN_IF_ERROR( - NodeBuilder("ConfigureDistributedTPU", "ConfigureDistributedTPU") - .Device(DEVICE_TPU_SYSTEM) - .Finalize(&graph_->graph, &config_node)); - *config_op = {ToTF_Operation(config_node), 0}; - } - - { - Node* shutdown_node; - TF_RETURN_IF_ERROR( - NodeBuilder("ShutdownDistributedTPU", "ShutdownDistributedTPU") - .Device(DEVICE_TPU_SYSTEM) - .Finalize(&graph_->graph, &shutdown_node)); - *shutdown_op = {ToTF_Operation(shutdown_node), 0}; - } - - return Status::OK(); - } - - TF_Graph* const graph_; - - const TF_Output* const input_nodes_; - - const std::string cluster_name_ = "TPUReplicate/cluster"; - const std::string metadata_node_name_ = "TPUReplicate/TPUReplicateMetadata"; - - // Keep mappings from the current input nodes to newly created input nodes, - // which we will use to rewrite existing nodes that read these - // inputs. e.g. A node that reads input node PlaceHolder could be rewired to - // read the created TPUReplicate/replicated_input_0 node or some output port - // of the created TPUReplicate/InfeedDequeueTuple node. Because of the latter - // case, we the map entries store NodeBuilder::NodeOut, and not just Node*. - std::unordered_map input_node_map_; - - std::vector nodes_to_rewrite_; - - // Map from name to set{(output port, output tensor idx)}. - // e.g. Say there are 3 output tensors, respectively produced by (node 0, - // port 0), (node 0, port 1), (node 1, port 0). Then the mapping entries - // are: node 0 -> {(port 0, idx 0), (port 1, idx 1)} node 1 -> {(port 0, idx - // 2)} Based on these mappings, we will generate 3 new output nodes. - struct PortIndexPair { - int port; - int index; - }; - std::multimap output_node_map_; -}; - -} // namespace void TF_EnableXLACompilation(TF_SessionOptions* options, unsigned char enable) { tensorflow::ConfigProto& config = options->options.config; @@ -425,62 +43,54 @@ void TF_EnableXLACompilation(TF_SessionOptions* options, unsigned char enable) { } } -TF_Output TF_SetupTPUExecution(TF_Session* session, int num_input_nodes, - const TF_Output* input_nodes, - int num_output_nodes, - const TF_Output* output_nodes, - TF_Output* new_output_nodes, - TF_Operation** infeed_enqueue_node, - TF_Status* status) { - TF_Output config_op, shutdown_op; - { - auto graph = session->graph; - tensorflow::mutex_lock c(graph->mu); - - VLOG(1) << "Graph before TPU rewrite: " - << graph->graph.ToGraphDefDebug().DebugString(); - GraphRewriter rewriter(graph, num_input_nodes, input_nodes, - num_output_nodes, output_nodes); - status->status = rewriter.Rewrite(new_output_nodes, infeed_enqueue_node, - &config_op, &shutdown_op); - if (!status->status.ok()) { - return shutdown_op; - } - VLOG(1) << "Graph after TPU rewrite: " - << graph->graph.ToGraphDefDebug().DebugString(); +void TF_InitializeTPU(TF_Session* session, TF_Status* status) { + VLOG(1) << "Initializing TPU"; + TF_Operation* config_op = + TF_GraphOperationByName(session->graph, "ConfigureDistributedTPU"); + if (config_op == nullptr) { + status->status = tensorflow::errors::Internal( + "Unable to find node ConfigureDistributedTPU in the TF graph."); + return; } - VLOG(1) << "Initializing TPU"; + TF_Output config_node{config_op, 0}; + TF_Tensor* dummy_output; TF_SessionRun(session, /*run_options*/ nullptr, // input related parameters /*inputs*/ nullptr, /*input_values*/ nullptr, /*ninputs*/ 0, // output related parameters - /*outputs*/ &config_op, /*output_values*/ &dummy_output, + /*outputs*/ &config_node, /*output_values*/ &dummy_output, /*noutputs*/ 1, /*targets*/ nullptr, /*ntargets*/ 0, /*run_metadata*/ nullptr, status); if (status->status.ok()) { TF_DeleteTensor(dummy_output); } - return shutdown_op; } -void TF_ShutdownTPUExecution(TF_Session* session, TF_Output shutdown_node, - TF_Status* status) { +void TF_ShutdownTPU(TF_Session* session, TF_Status* status) { { tensorflow::mutex_lock c(session->graph->mu); VLOG(1) << "Shutting down TPU, with input graph: " << session->graph->graph.ToGraphDefDebug().DebugString(); } + TF_Operation* shutdown_op = + TF_GraphOperationByName(session->graph, "ShutdownDistributedTPU"); + if (shutdown_op == nullptr) { + status->status = tensorflow::errors::Internal( + "Unable to find node ShutdownDistributedTPU in the TF graph."); + return; + } + TF_SessionRun(session, /*run_options*/ nullptr, // input related parameters /*inputs*/ nullptr, /*input_values*/ nullptr, /*ninputs*/ 0, // output related parameters /*outputs*/ nullptr, /*output_values*/ nullptr, /*noutputs*/ 0, - /*targets*/ &shutdown_node.oper, /*ntargets*/ 1, + /*targets*/ &shutdown_op, /*ntargets*/ 1, /*run_metadata*/ nullptr, status); } diff --git a/tensorflow/c/c_api_experimental.h b/tensorflow/c/c_api_experimental.h index b95cdfe6aa..f069398bbb 100644 --- a/tensorflow/c/c_api_experimental.h +++ b/tensorflow/c/c_api_experimental.h @@ -60,39 +60,26 @@ extern "C" { TF_CAPI_EXPORT extern void TF_EnableXLACompilation(TF_SessionOptions* options, unsigned char enable); -// Sets up TPU execution, by rewriting the graph accordingly, and initializing -// TPU system. +// Initializes TPU system. Must be called exactly once before TF_SessionRun() is +// called on a TPU graph. // -// When `infeed_enqueue_node` is non-NULL and there are input tensors, rewrites -// the graph by adding the relevant infeed enqueue/dequeue ops, and returns the -// enqueue op in `infeed_enqueue_node` on success, so that user can run that -// node and feed input tensors. When there are no input tensors, -// `infeed_enqueue_node` is ignored, and user should not run that node later. -// TODO(hongm): In this case, we currently only support input tensors of dim 0 -// shape. Lift that constraint. -// -// On success, also returns a shutdown node to be used in a subsequent -// TF_ShutdownTPUExecution(), and sets the new output nodes in -// `new_output_nodes` for caller to fetch from. Must be called exactly once -// before TF_SessionRun(). -// -// The API and logic is modeled after the python counterparts -// tpu.{initialize_system(), rewrite(), shutdown_system()}. -// -// TODO(b/74774824): Create separate APIs for initializing TPU system and graph -// rewrite. -TF_CAPI_EXPORT extern TF_Output TF_SetupTPUExecution( - TF_Session* session, int num_input_nodes, const TF_Output* input_nodes, - int num_output_nodes, const TF_Output* output_nodes, - TF_Output* new_output_nodes, TF_Operation** infeed_enqueue_node, - TF_Status* status); - -// Shuts down TPU system. For any `session` where TF_SetupTPUExecution() has +// The session graph must contain a node named ConfigureDistributedTPU. +// TODO(b/74774824): Improve the API on initializing TPU system. +TF_CAPI_EXPORT extern void TF_InitializeTPU(TF_Session* session, + TF_Status* status); + +// Shuts down TPU system. For any `session` where TF_InitializeTPU() has // been successfully called, this call must be made exactly once before the // session is closed. -TF_CAPI_EXPORT extern void TF_ShutdownTPUExecution(TF_Session* session, - TF_Output shutdown_node, - TF_Status* status); +// The session graph must contain a node named ShutdownDistributedTPU. +TF_CAPI_EXPORT extern void TF_ShutdownTPU(TF_Session* session, + TF_Status* status); + +// Returns the graph content in a human-readable format, with length set in +// `len`. The format is subject to change in the future. +// The returned string is heap-allocated, and caller should call free() on it. +TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, + size_t* len); // Returns the graph content in a human-readable format, with length set in // `len`. The format is subject to change in the future. -- GitLab From 0e1775355f9d7fe5301bc0d17906453caf970e27 Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Wed, 21 Mar 2018 23:04:59 -0700 Subject: [PATCH 058/906] Merge changes from github. PiperOrigin-RevId: 190020572 --- .../data/python/kernel_tests/resample_test.py | 40 +++++++++++++++++++ .../contrib/data/python/ops/resampling.py | 9 +++-- .../core/kernels/segment_reduction_ops.h | 8 ---- .../docs_src/tutorials/kernel_methods.md | 4 +- .../docs_src/tutorials/recurrent_quickdraw.md | 8 ++-- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/tensorflow/contrib/data/python/kernel_tests/resample_test.py b/tensorflow/contrib/data/python/kernel_tests/resample_test.py index 913ab9b9f8..5f47dcb339 100644 --- a/tensorflow/contrib/data/python/kernel_tests/resample_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/resample_test.py @@ -21,7 +21,10 @@ import numpy as np from tensorflow.contrib.data.python.ops import resampling from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import random_ops from tensorflow.python.ops import string_ops from tensorflow.python.platform import test from tensorflow.python.util import compat @@ -68,6 +71,43 @@ class ResampleTest(test.TestCase): returned_dist = class_counts / total_returned self.assertAllClose(target_dist, returned_dist, atol=1e-2) + def testRandomClasses(self): + init_dist = [0.25, 0.25, 0.25, 0.25] + target_dist = [0.0, 0.0, 0.0, 1.0] + num_classes = len(init_dist) + # We don't need many samples to test a dirac-delta target distribution + num_samples = 100 + data_np = np.random.choice(num_classes, num_samples, p=init_dist) + + dataset = dataset_ops.Dataset.from_tensor_slices(data_np) + + # Apply a random mapping that preserves the data distribution. + def _remap_fn(_): + return math_ops.cast(random_ops.random_uniform([1]) * num_classes, + dtypes.int32)[0] + dataset = dataset.map(_remap_fn) + + # Reshape distribution. + dataset = dataset.apply( + resampling.rejection_resample( + class_func=lambda x: x, + target_dist=target_dist, + initial_dist=init_dist)) + + get_next = dataset.make_one_shot_iterator().get_next() + + with self.test_session() as sess: + returned = [] + with self.assertRaises(errors.OutOfRangeError): + while True: + returned.append(sess.run(get_next)) + + classes, _ = zip(*returned) + bincount = np.bincount( + np.array(classes), + minlength=num_classes).astype(np.float32) / len(classes) + + self.assertAllClose(target_dist, bincount, atol=1e-2) if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/data/python/ops/resampling.py b/tensorflow/contrib/data/python/ops/resampling.py index f4015f19fb..a182dddd38 100644 --- a/tensorflow/contrib/data/python/ops/resampling.py +++ b/tensorflow/contrib/data/python/ops/resampling.py @@ -101,11 +101,12 @@ def rejection_resample(class_func, target_dist, initial_dist=None, seed=None): initial_dist_ds)) .map(maybe_warn_on_large_rejection)) - current_probabilities_ds = dataset_ops.Dataset.zip( - (acceptance_dist_ds, class_values_ds)).map(array_ops.gather) + def _gather_and_copy(class_val, acceptance_prob, data): + return (class_val, array_ops.gather(acceptance_prob, class_val), data) + current_probabilities_and_class_and_data_ds = dataset_ops.Dataset.zip( + (class_values_ds, acceptance_dist_ds, dataset)).map(_gather_and_copy) filtered_ds = ( - dataset_ops.Dataset.zip((class_values_ds, current_probabilities_ds, - dataset)) + current_probabilities_and_class_and_data_ds .filter(lambda _1, p, _2: random_ops.random_uniform([], seed=seed) < p)) return filtered_ds.map(lambda class_value, _, data: (class_value, data)) diff --git a/tensorflow/core/kernels/segment_reduction_ops.h b/tensorflow/core/kernels/segment_reduction_ops.h index d65692a552..4abfbfb1a6 100644 --- a/tensorflow/core/kernels/segment_reduction_ops.h +++ b/tensorflow/core/kernels/segment_reduction_ops.h @@ -16,14 +16,6 @@ limitations under the License. #ifndef TENSORFLOW_CORE_KERNELS_SEGMENT_REDUCTION_OPS_H_ #define TENSORFLOW_CORE_KERNELS_SEGMENT_REDUCTION_OPS_H_ - -// This file requires the following include because it uses CudaAtomicMax: -// #include "tensorflow/core/util/cuda_kernel_helper.h" - -// Unfortunately we can't add the #include, since it breaks compilation for -// non-GPU targets. This only breaks in clang, because it's more strict for -// template code and CudaAtomicMax is used in template context. - // This file requires the following include because it uses CudaAtomicMax: // #include "tensorflow/core/util/cuda_kernel_helper.h" diff --git a/tensorflow/docs_src/tutorials/kernel_methods.md b/tensorflow/docs_src/tutorials/kernel_methods.md index b1f06ce0a3..73e5c51057 100644 --- a/tensorflow/docs_src/tutorials/kernel_methods.md +++ b/tensorflow/docs_src/tutorials/kernel_methods.md @@ -1,7 +1,7 @@ # Improving Linear Models Using Explicit Kernel Methods Note: This document uses a deprecated version of @{tf.estimator}, -which has a different interface (see `tf.contrib.learn Estimator`). +which has a @{tf.contrib.learn.Estimator$different interface}. It also uses other `contrib` methods whose @{$version_compat#not_covered$API may not be stable}. @@ -53,7 +53,7 @@ In order to feed data to a `tf.contrib.learn Estimator`, it is helpful to conver it to Tensors. For this, we will use an `input function` which adds Ops to the TensorFlow graph that, when executed, create mini-batches of Tensors to be used downstream. For more background on input functions, check -@{$get_started/premade_estimators#input_fn$this section on input functions}. +@{$get_started/premade_estimators#create_input_functions$this section on input functions}. In this example, we will use the `tf.train.shuffle_batch` Op which, besides converting numpy arrays to Tensors, allows us to specify the batch_size and whether to randomize the input every time the input_fn Ops are executed diff --git a/tensorflow/docs_src/tutorials/recurrent_quickdraw.md b/tensorflow/docs_src/tutorials/recurrent_quickdraw.md index 7584a76ba5..5d83fbe2a3 100644 --- a/tensorflow/docs_src/tutorials/recurrent_quickdraw.md +++ b/tensorflow/docs_src/tutorials/recurrent_quickdraw.md @@ -38,8 +38,8 @@ To try the code for this tutorial: 1. [Download the data](#download-the-data) in `TFRecord` format from [here](http://download.tensorflow.org/data/quickdraw_tutorial_dataset_v1.tar.gz) and unzip it. More details about [how to obtain the original Quick, Draw! - data](#optional-download-the-full-quick-draw-data) and [how to convert that - to `TFRecord` files](#optional-converting-the-data) is available below. + data](#optional_download_the_full_quick_draw_data) and [how to convert that + to `TFRecord` files](#optional_converting_the_data) is available below. 1. Execute the tutorial code with the following command to train the RNN-based model described in this tutorial. Make sure to adjust the paths to point to @@ -108,7 +108,7 @@ This download will take a while and download a bit more than 23GB of data. ### Optional: Converting the data To convert the `ndjson` files to -@{$python/python_io#tfrecords_format_details$TFRecord} files containing +@{$python/python_io#TFRecords_Format_Details$TFRecord} files containing [`tf.train.Example`](https://www.tensorflow.org/code/tensorflow/core/example/example.proto) protos run the following command. @@ -118,7 +118,7 @@ protos run the following command. ``` This will store the data in 10 shards of -@{$python/python_io#tfrecords_format_details$TFRecord} files with 10000 items +@{$python/python_io#TFRecords_Format_Details$TFRecord} files with 10000 items per class for the training data and 1000 items per class as eval data. This conversion process is described in more detail in the following. -- GitLab From f83711104b64a108ac43213c92f13827343d09ef Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 21 Mar 2018 23:11:40 -0700 Subject: [PATCH 059/906] Automated g4 rollback of changelist 190001737 PiperOrigin-RevId: 190021164 --- tensorflow/c/eager/BUILD | 2 - tensorflow/c/eager/c_api.cc | 179 ++++++++++------ tensorflow/c/eager/c_api_internal.h | 84 +++++++- tensorflow/core/common_runtime/eager/BUILD | 22 -- .../core/common_runtime/eager/context.cc | 142 ------------- .../core/common_runtime/eager/context.h | 193 ------------------ 6 files changed, 194 insertions(+), 428 deletions(-) delete mode 100644 tensorflow/core/common_runtime/eager/context.cc delete mode 100644 tensorflow/core/common_runtime/eager/context.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index bea5a121b3..841ff48a38 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -28,7 +28,6 @@ tf_cuda_library( "//tensorflow/c:c_api", "//tensorflow/c:c_api_internal", "//tensorflow/core:core_cpu", - "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", "//tensorflow/core:core_cpu_internal", @@ -65,7 +64,6 @@ tf_cuda_library( "//tensorflow/core:framework_lite", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", - "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", ], diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 5d668848ab..a23015c99e 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -71,6 +71,18 @@ std::atomic_int_fast64_t func_id_generator(0); } // namespace +TFE_ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy) { + if (!soft_placement) { + return original_policy; + } + if (original_policy == TFE_DEVICE_PLACEMENT_EXPLICIT || + original_policy == TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32) { + return TFE_DEVICE_PLACEMENT_SILENT; + } + return original_policy; +} + extern "C" { TFE_ContextOptions* TFE_NewContextOptions() { return new TFE_ContextOptions; } @@ -92,7 +104,19 @@ void TFE_ContextOptionsSetDevicePlacementPolicy( TF_CAPI_EXPORT extern void TFE_ContextSetAsyncForThread(TFE_Context* ctx, unsigned char async, TF_Status* status) { - status->status = ctx->context.SetAsyncForThread(async); + { + tensorflow::mutex_lock l(ctx->async_map_mu); + ctx->thread_local_async[std::this_thread::get_id()] = async; + } + if (async) { + ctx->executor.EnableAsync(); + } else { + // TODO(agarwal): Currently we add a wait here to handle cases where a sync + // op has a control dependency on an async op, and the latter has not + // executed yet. This wait can be removed by storing all the control inputs + // and waiting for them when executing ops. + status->status = ctx->executor.WaitForAllPendingNodes(); + } } void TFE_DeleteContextOptions(TFE_ContextOptions* options) { delete options; } @@ -109,26 +133,34 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) { new tensorflow::DeviceMgr(devices)); tensorflow::Rendezvous* r = new tensorflow::IntraProcessRendezvous(device_mgr.get()); - return new TFE_Context(opts->session_options.options, opts->policy, - opts->async, std::move(device_mgr), r); + return new TFE_Context(*opts, std::move(device_mgr), r); } void TFE_DeleteContext(TFE_Context* ctx, TF_Status* status) { + status->status = ctx->executor.WaitForAllPendingNodes(); + { + tensorflow::mutex_lock ml(ctx->cache_mu); + tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); + } + ctx->rendezvous->Unref(); delete ctx; } TF_DeviceList* TFE_ContextListDevices(TFE_Context* ctx, TF_Status* status) { TF_DeviceList* list = new TF_DeviceList; - ctx->context.device_mgr()->ListDeviceAttributes(&list->response); + ctx->device_manager->ListDeviceAttributes(&list->response); return list; } -void TFE_ContextClearCaches(TFE_Context* ctx) { ctx->context.ClearCaches(); } +void TFE_ContextClearCaches(TFE_Context* ctx) { + tensorflow::mutex_lock ml(ctx->cache_mu); + tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); +} void TFE_ContextSetThreadLocalDevicePlacementPolicy( TFE_Context* ctx, TFE_ContextDevicePlacementPolicy policy) { - ctx->context.SetThreadLocalDevicePlacementPolicy( - static_cast(policy)); + tensorflow::mutex_lock ml(ctx->policy_map_mu); + ctx->thread_local_policies[std::this_thread::get_id()] = policy; } // Note: this function looks up a thread local policy. So it should be called in @@ -136,20 +168,25 @@ void TFE_ContextSetThreadLocalDevicePlacementPolicy( // safe to call this function from the async EagerExecutor threads. extern TFE_ContextDevicePlacementPolicy TFE_ContextGetDevicePlacementPolicy( TFE_Context* ctx) { - return static_cast( - ctx->context.GetDevicePlacementPolicy()); + tensorflow::mutex_lock ml(ctx->policy_map_mu); + auto policy_map_it = + ctx->thread_local_policies.find(std::this_thread::get_id()); + if (policy_map_it != ctx->thread_local_policies.end()) { + return policy_map_it->second; + } + return ctx->policy; } void TFE_ContextAsyncWait(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->context.AsyncWait(); + status->status = ctx->executor.WaitForAllPendingNodes(); } void TFE_ContextGetStatus(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->context.GetStatus(); + status->status = ctx->executor.status(); } void TFE_ContextAsyncClearError(TFE_Context* ctx) { - ctx->context.ClearAsyncError(); + ctx->executor.ClearError(); } TFE_TensorHandle* TFE_NewTensorHandle(TF_Tensor* t, TF_Status* status) { @@ -222,7 +259,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, // nullptr. tensorflow::Device* src_opd = nullptr; TF_RETURN_IF_ERROR(h->TensorAndDevice(&src, &srcd, &src_opd)); - if (srcd == nullptr) srcd = ctx->context.HostCPU(); + if (srcd == nullptr) srcd = ctx->devices[0]; bool is_same_device = (srcd == dstd) || (DeviceName(srcd) == DeviceName(dstd)); const bool dst_cpu = IsCPU(dstd); @@ -295,7 +332,8 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, status->status = tensorflow::AttrTypeMapForOp(name, &types); if (status->status.ok()) return new TFE_Op(ctx, name, types); if (TF_GetCode(status) == TF_NOT_FOUND) { - if (ctx->context.FindFunctionByName(name)) { + tensorflow::mutex_lock l(ctx->functions_mu); + if (ctx->func_lib_def.Find(name) != nullptr) { status->status = tensorflow::Status::OK(); return new TFE_Op(ctx, name, nullptr); } @@ -308,14 +346,20 @@ void TFE_DeleteOp(TFE_Op* op) { delete op; } void TFE_OpSetDevice(TFE_Op* op, const char* device_name, TF_Status* status) { tensorflow::Device* d = nullptr; if (device_name != nullptr && strlen(device_name) > 0) { - status->status = op->ctx->context.FindDeviceByName(device_name, &d); + auto it = op->ctx->devices_map.find(device_name); + if (it == op->ctx->devices_map.end()) { + status->status = + tensorflow::errors::InvalidArgument(device_name, " unknown device."); + return; + } + d = it->second; } op->device = d; } const char* TFE_OpGetDevice(TFE_Op* op, TF_Status* status) { tensorflow::Device* device = - (op->device == nullptr) ? op->ctx->context.HostCPU() : op->device; + (op->device == nullptr) ? op->ctx->devices[0] : op->device; return device->name().c_str(); } @@ -590,7 +634,7 @@ tensorflow::Status ValidateInputTypeAndPlacement( tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, TFE_Context* ctx, TF_Status* status) { tensorflow::DeviceSet ds; - for (tensorflow::Device* d : *ctx->context.devices()) { + for (tensorflow::Device* d : ctx->devices) { ds.AddDevice(d); } tensorflow::DeviceTypeVector final_devices; @@ -604,7 +648,7 @@ tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, "Could not find valid device for node ", ndef.DebugString()); return nullptr; } - for (tensorflow::Device* d : *ctx->context.devices()) { + for (tensorflow::Device* d : ctx->devices) { if (d->device_type() == final_devices[0].type_string()) { return d; } @@ -619,8 +663,9 @@ tensorflow::Status Execute( const tensorflow::gtl::InlinedVector& op_inputs, tensorflow::KernelAndDevice* kernel, tensorflow::NodeExecStats* maybe_stats, TFE_TensorHandle** retvals, int num_retvals) { - if (!ctx->context.SoftPlacement() && device == nullptr) { - device = ctx->context.HostCPU(); + if (!ctx->soft_placement && device == nullptr) { + // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU + device = ctx->devices[0]; } if (device == nullptr) { @@ -652,18 +697,18 @@ tensorflow::Status Execute( if (maybe_stats != nullptr) { maybe_stats->set_op_end_rel_micros(tensorflow::Env::Default()->NowMicros() - maybe_stats->all_start_micros()); - tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); - if (ctx->context.ShouldStoreMetadata()) { - auto* step_stats = ctx->context.RunMetadataProto()->mutable_step_stats(); + tensorflow::mutex_lock ml(ctx->metadata_mu); + if (ctx->should_store_metadata.load()) { + auto* step_stats = ctx->run_metadata.mutable_step_stats(); // Lazily initialize the RunMetadata with information about all devices if // this is the first call. - while (step_stats->dev_stats_size() < ctx->context.devices()->size()) { + while (step_stats->dev_stats_size() < ctx->devices.size()) { step_stats->add_dev_stats(); } // Find the current device's index. int device_idx = 0; - for (int i = 0; i < ctx->context.devices()->size(); ++i) { - if (ctx->context.devices()->at(i) == device) { + for (int i = 0; i < ctx->devices.size(); ++i) { + if (ctx->devices[i] == device) { device_idx = i; break; } @@ -699,7 +744,7 @@ class ExecuteNode : public tensorflow::EagerNode { tensorflow::NodeExecStats* maybe_stats, const tensorflow::DataTypeVector& output_dtypes, TFE_TensorHandle** retvals, int num_retvals) - : tensorflow::EagerNode(op->ctx->context.NextId()), + : tensorflow::EagerNode(op->ctx->executor.NextId()), ctx_(op->ctx), op_device_(op->device), inputs_(op->inputs), @@ -755,7 +800,7 @@ class CopyToDeviceNode : public tensorflow::EagerNode { public: CopyToDeviceNode(TFE_TensorHandle* src, tensorflow::Device* dstd, TFE_Context* ctx) - : tensorflow::EagerNode(ctx->context.NextId()), + : tensorflow::EagerNode(ctx->executor.NextId()), src_(src), dstd_(dstd), ctx_(ctx), @@ -1018,7 +1063,7 @@ extern "C" { void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, TF_Status* status) { TFE_Context* ctx = op->ctx; - status->status = ctx->context.GetStatus(); + status->status = ctx->executor.status(); if (!status->status.ok()) { return; } @@ -1042,7 +1087,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (op->inputs[i]->dtype == tensorflow::DT_RESOURCE && input_op_device != op->device) { tensorflow::Device* d = - input_op_device == nullptr ? ctx->context.HostCPU() : input_op_device; + input_op_device == nullptr ? ctx->devices[0] : input_op_device; VLOG(1) << "Changing device of operation " << op->name << " to " << d->name() << " because input #" << i << " is a resource in this device."; @@ -1050,35 +1095,40 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, } } tensorflow::Device* device = op->device; - if (!ctx->context.SoftPlacement() && device == nullptr) { - device = ctx->context.HostCPU(); + if (!ctx->soft_placement && device == nullptr) { + // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU + device = ctx->devices[0]; } tensorflow::Fprint128 cache_key = op->attrs.CacheKey(device == nullptr ? "unspecified" : device->name()); - tensorflow::KernelAndDevice* kernel = ctx->context.GetCachedKernel(cache_key); + tensorflow::KernelAndDevice* kernel; + { + tensorflow::tf_shared_lock l(ctx->cache_mu); + kernel = tensorflow::gtl::FindPtrOrNull(ctx->kernel_cache, cache_key); + } if (kernel == nullptr) { const tensorflow::NodeDef& ndef = op->attrs.BuildNodeDef(); - if (ctx->context.SoftPlacement() && device == nullptr) { + if (ctx->soft_placement && device == nullptr) { device = SelectDevice(ndef, ctx, status); if (!status->status.ok()) { return; } } CHECK(device != nullptr); - if (ctx->context.LogDevicePlacement()) { + if (ctx->log_device_placement) { LOG(INFO) << "Executing op " << ndef.op() << " in device " << device->name(); } - kernel = new tensorflow::KernelAndDevice(ctx->context.GetRendezvous()); + kernel = new tensorflow::KernelAndDevice(ctx->rendezvous); // Knowledge of the implementation of Init (and in-turn // FunctionLibraryRuntime::CreateKernel) tells us that ctx->func_lib_def // will be accessed, so grab on to the lock. // See WARNING comment in Execute (before kernel->Run) - would be nice to // rework to avoid this subtlety. - tensorflow::tf_shared_lock l(*ctx->context.FunctionsMu()); - status->status = tensorflow::KernelAndDevice::Init( - ndef, ctx->context.func_lib(device), kernel); + tensorflow::tf_shared_lock l(ctx->functions_mu); + status->status = + tensorflow::KernelAndDevice::Init(ndef, ctx->func_lib(device), kernel); if (!status->status.ok()) { delete kernel; return; @@ -1086,7 +1136,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // Update output_dtypes inside `kernel`. const tensorflow::OpDef* op_def = nullptr; const tensorflow::FunctionDef* function_def = - ctx->context.FuncLibDef()->Find(ndef.op()); + ctx->func_lib_def.Find(ndef.op()); if (function_def != nullptr) { op_def = &(function_def->signature()); } @@ -1102,7 +1152,8 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (!status->status.ok()) { return; } - ctx->context.AddKernelToCache(cache_key, kernel); + tensorflow::mutex_lock ml(ctx->cache_mu); + tensorflow::gtl::InsertOrUpdate(&(ctx->kernel_cache), cache_key, kernel); } const tensorflow::DataTypeVector& output_dtypes = kernel->output_dtypes(); const int output_dtypes_size = output_dtypes.size(); @@ -1120,11 +1171,11 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // device from the one requested above. device = kernel->device(); } - status->status = ValidateInputTypeAndPlacement(ctx, ctx->context.HostCPU(), - device, op, kernel->kernel()); + status->status = ValidateInputTypeAndPlacement(ctx, ctx->devices[0], device, + op, kernel->kernel()); if (!status->status.ok()) return; std::unique_ptr maybe_stats; - if (ctx->context.ShouldStoreMetadata()) { + if (ctx->should_store_metadata.load()) { maybe_stats.reset(new tensorflow::NodeExecStats); maybe_stats->set_node_name(op->name); maybe_stats->set_all_start_micros(tensorflow::Env::Default()->NowMicros()); @@ -1132,14 +1183,14 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, maybe_stats->set_scheduled_micros(tensorflow::Env::Default()->NowMicros()); // TODO(apassos) track referenced tensors } - if (ctx->context.Async()) { + if (ctx->Async()) { // Note that for async mode, execution order will make sure that all // input handles are ready before executing them. // TODO(agarwal): Consider executing "cheap" kernels inline for performance. tensorflow::EagerNode* node = new ExecuteNode(op, kernel, maybe_stats.release(), output_dtypes, retvals, *num_retvals); - ctx->context.ExecutorAdd(node); + ctx->executor.Add(node); } else { // Execute checks if retvals[i] is nullptr or not to figure if it needs to // allocate it. @@ -1155,24 +1206,23 @@ TFE_TensorHandle* TFE_TensorHandleCopyToDevice(TFE_TensorHandle* h, TFE_Context* ctx, const char* device_name, TF_Status* status) { - status->status = ctx->context.GetStatus(); + status->status = ctx->executor.status(); if (!status->status.ok()) { return nullptr; } - tensorflow::Device* dstd = ctx->context.HostCPU(); + tensorflow::Device* dstd = ctx->devices[0]; if (device_name != nullptr && strlen(device_name) > 0) { - status->status = - ctx->context.device_mgr()->LookupDevice(device_name, &dstd); + status->status = ctx->device_manager->LookupDevice(device_name, &dstd); if (!status->status.ok()) return nullptr; } - if (ctx->context.Async()) { + if (ctx->Async()) { // Note that `h` may not be currently ready. However execution order will // make sure that `h` is ready before the copy is actually done. CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); TFE_TensorHandle* output = node->dst(); // Note that calling Add makes `node` accessible by the EagerExecutor // thread. So further accesses need to be thread-safe. - ctx->context.ExecutorAdd(node); + ctx->executor.Add(node); return output; } else { TFE_TensorHandle* output = nullptr; @@ -1190,20 +1240,24 @@ void TFE_ContextAddFunctionDef(TFE_Context* ctx, tensorflow::errors::InvalidArgument("Invalid FunctionDef proto"); return; } - status->status = ctx->context.AddFunctionDef(function_def); + tensorflow::mutex_lock l(ctx->functions_mu); + status->status = ctx->func_lib_def.AddFunctionDef(function_def); } void TFE_ContextAddFunction(TFE_Context* ctx, TF_Function* function, TF_Status* status) { - status->status = ctx->context.AddFunctionDef(function->fdef); + tensorflow::mutex_lock l(ctx->functions_mu); + status->status = ctx->func_lib_def.AddFunctionDef(function->fdef); } void TFE_ContextEnableRunMetadata(TFE_Context* ctx) { - ctx->context.SetShouldStoreMetadata(true); + ctx->should_store_metadata.store(true); } void TFE_ContextDisableRunMetadata(TFE_Context* ctx) { - ctx->context.SetShouldStoreMetadata(false); + tensorflow::mutex_lock ml(ctx->metadata_mu); + ctx->should_store_metadata.store(false); + ctx->run_metadata.Clear(); } } // extern "C" @@ -1232,9 +1286,9 @@ void TFE_ContextExportRunMetadata(TFE_Context* ctx, TF_Buffer* buf, TF_Status* status) { TFE_ContextAsyncWait(ctx, status); if (!status->status.ok()) return; - tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); - status->status = MessageToBuffer(*ctx->context.RunMetadataProto(), buf); - ctx->context.RunMetadataProto()->Clear(); + tensorflow::mutex_lock ml(ctx->metadata_mu); + status->status = MessageToBuffer(ctx->run_metadata, buf); + ctx->run_metadata.Clear(); } namespace { @@ -1309,6 +1363,11 @@ void SetOpAttrValueScalar(TFE_Context* ctx, TFE_Op* op, } // namespace tensorflow +bool TFE_Context::Async() const { + tensorflow::mutex_lock l(async_map_mu); + return tensorflow::gtl::FindWithDefault( + thread_local_async, std::this_thread::get_id(), async_default); +} bool TFE_TensorHandle::IsReady() { if (node_id == 0) return true; @@ -1322,7 +1381,7 @@ tensorflow::Status TFE_TensorHandle::WaitReady() { { tensorflow::mutex_lock l(ctx_mutex_); if (ctx_ == nullptr) return tensorflow::Status::OK(); - executor = ctx_->context.Executor(); + executor = &ctx_->executor; } return executor->WaitFor(node_id); } diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index 5b29120b40..a79f8ddd33 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -30,7 +30,6 @@ limitations under the License. #include "tensorflow/c/c_api_internal.h" #include "tensorflow/c/eager/runtime.h" #include "tensorflow/core/common_runtime/device_factory.h" -#include "tensorflow/core/common_runtime/eager/context.h" #include "tensorflow/core/common_runtime/eager/eager_executor.h" #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" #include "tensorflow/core/common_runtime/function.h" @@ -53,18 +52,85 @@ struct TFE_ContextOptions { TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32}; }; +TFE_ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy); + struct TFE_Context { - explicit TFE_Context(const tensorflow::SessionOptions& opts, - TFE_ContextDevicePlacementPolicy default_policy, - bool async, + explicit TFE_Context(const TFE_ContextOptions& opts, std::unique_ptr device_mgr, tensorflow::Rendezvous* rendezvous) - : context(opts, - static_cast( - default_policy), - async, std::move(device_mgr), rendezvous) {} + : soft_placement( + opts.session_options.options.config.allow_soft_placement()), + policy(PlacementPolicy(soft_placement, opts.policy)), + device_manager(std::move(device_mgr)), + devices(device_manager->ListDevices()), + rendezvous(rendezvous), + pflr(new tensorflow::ProcessFunctionLibraryRuntime( + device_manager.get(), opts.session_options.options.env, + TF_GRAPH_DEF_VERSION, &func_lib_def, {})), + log_device_placement( + opts.session_options.options.config.log_device_placement()), + async_default(opts.async) { + if (async_default) executor.EnableAsync(); + + for (auto* device : devices) { + devices_map[tensorflow::StringPiece(device->name())] = device; + } + } + + const bool soft_placement; + const TFE_ContextDevicePlacementPolicy policy; + + // Note: we cannot use C++11 thread_local here as there is no concept of a + // thread-local-object-local variable in C++11. + tensorflow::mutex policy_map_mu; + std::unordered_map + thread_local_policies GUARDED_BY(policy_map_mu); + + std::unique_ptr device_manager; + // Devices owned by device_manager + std::vector devices; + // All devices are not owned. + tensorflow::gtl::FlatMap + devices_map; + tensorflow::Rendezvous* const rendezvous; + + tensorflow::mutex functions_mu; + tensorflow::FunctionLibraryDefinition func_lib_def GUARDED_BY(functions_mu){ + tensorflow::OpRegistry::Global(), {}}; + + // One FunctionLibraryRuntime per device. + // func_libs[i] is the FunctionLibraryRuntime corresponding to + // session->devices[i]. + const std::unique_ptr pflr; + + tensorflow::mutex cache_mu; + std::unordered_map + kernel_cache GUARDED_BY(cache_mu); + + tensorflow::FunctionLibraryRuntime* func_lib(tensorflow::Device* d) const { + return pflr->GetFLR(d->name()); + } - tensorflow::EagerContext context; + // Whether we should compute RunMetadata. + std::atomic should_store_metadata{false}; + tensorflow::mutex metadata_mu; + tensorflow::RunMetadata run_metadata GUARDED_BY(metadata_mu); + const bool log_device_placement; + // EagerExecutor for async execution. + tensorflow::EagerExecutor executor; + + // True if running in asynchronous mode. + bool Async() const; + + // True if the default value for execution mode is async. Note that this value + // can be overridden per thread based on `thread_local_async` overrides. + const bool async_default; + mutable tensorflow::mutex async_map_mu; + std::unordered_map thread_local_async + GUARDED_BY(async_map_mu); }; struct TFE_TensorHandle : public tensorflow::core::RefCounted { diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index de10b10b7e..8ba560bef8 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -32,28 +32,6 @@ tf_cuda_library( ], ) -tf_cuda_library( - name = "context", - srcs = [ - "context.cc", - ], - hdrs = [ - "context.h", - ], - visibility = ["//tensorflow:internal"], - deps = [ - ":eager_executor", - ":kernel_and_device", - "//tensorflow/core:core_cpu_lib", - "//tensorflow/core:framework", - "//tensorflow/core:framework_internal", - "//tensorflow/core:lib", - "//tensorflow/core:lib_internal", - "//tensorflow/core:protos_all_cc", - "//tensorflow/core:session_options", - ], -) - tf_cuda_library( name = "kernel_and_device", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc deleted file mode 100644 index 5e8d083cd2..0000000000 --- a/tensorflow/core/common_runtime/eager/context.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -#include "tensorflow/core/common_runtime/eager/context.h" - -namespace tensorflow { - -ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, ContextDevicePlacementPolicy original_policy) { - if (!soft_placement) { - return original_policy; - } - if (original_policy == DEVICE_PLACEMENT_EXPLICIT || - original_policy == DEVICE_PLACEMENT_SILENT_FOR_INT32) { - return DEVICE_PLACEMENT_SILENT; - } - return original_policy; -} - -EagerContext::EagerContext(const SessionOptions& opts, - ContextDevicePlacementPolicy default_policy, - bool async, std::unique_ptr device_mgr, - Rendezvous* rendezvous) - : soft_placement_(opts.config.allow_soft_placement()), - policy_(PlacementPolicy(soft_placement_, default_policy)), - device_manager_(std::move(device_mgr)), - devices_(device_manager_->ListDevices()), - rendezvous_(rendezvous), - pflr_(new ProcessFunctionLibraryRuntime(device_manager_.get(), opts.env, - TF_GRAPH_DEF_VERSION, - &func_lib_def_, {})), - log_device_placement_(opts.config.log_device_placement()), - async_default_(async) { - if (async_default_) { - executor_.EnableAsync(); - } - - for (auto* device : devices_) { - devices_map_[device->name()] = device; - } -} - -bool EagerContext::Async() const { - mutex_lock l(async_map_mu_); - return gtl::FindWithDefault(thread_local_async_, std::this_thread::get_id(), - async_default_); -} - -Status EagerContext::SetAsyncForThread(bool async) { - { - tensorflow::mutex_lock l(async_map_mu_); - thread_local_async_[std::this_thread::get_id()] = async; - } - if (async) { - executor_.EnableAsync(); - } else { - // TODO(agarwal): Currently we add a wait here to handle cases where a - // sync op has a control dependency on an async op, and the latter has not - // executed yet. This wait can be removed by storing all the control - // inputs and waiting for them when executing ops. - return executor_.WaitForAllPendingNodes(); - } - return Status::OK(); -} - -void EagerContext::ClearCaches() { - mutex_lock ml(cache_mu_); - gtl::STLDeleteValues(&kernel_cache_); -} - -void EagerContext::SetThreadLocalDevicePlacementPolicy( - ContextDevicePlacementPolicy policy) { - mutex_lock ml(policy_map_mu_); - thread_local_policies_[std::this_thread::get_id()] = policy; -} - -ContextDevicePlacementPolicy EagerContext::GetDevicePlacementPolicy() { - mutex_lock ml(policy_map_mu_); - auto policy_map_it = thread_local_policies_.find(std::this_thread::get_id()); - if (policy_map_it != thread_local_policies_.end()) { - return policy_map_it->second; - } - return policy_; -} - -EagerContext::~EagerContext() { - executor_.WaitForAllPendingNodes().IgnoreError(); - ClearCaches(); - rendezvous_->Unref(); -} - -bool EagerContext::FindFunctionByName(const string& name) { - mutex_lock l(functions_mu_); - return func_lib_def_.Find(name) != nullptr; -} - -Status EagerContext::FindDeviceByName(const string& name, Device** result) { - auto it = devices_map_.find(name); - if (it == devices_map_.end()) { - return errors::InvalidArgument(name, " unknown device."); - } - *result = it->second; - return Status::OK(); -} - -Status EagerContext::AddFunctionDef(const FunctionDef& fdef) { - mutex_lock l(functions_mu_); - return func_lib_def_.AddFunctionDef(fdef); -} - -KernelAndDevice* EagerContext::GetCachedKernel(Fprint128 cache_key) { - tf_shared_lock l(cache_mu_); - return gtl::FindPtrOrNull(kernel_cache_, cache_key); -} - -void EagerContext::AddKernelToCache(Fprint128 cache_key, - KernelAndDevice* kernel) { - mutex_lock ml(cache_mu_); - gtl::InsertOrUpdate(&kernel_cache_, cache_key, kernel); -} - -void EagerContext::SetShouldStoreMetadata(bool value) { - should_store_metadata_.store(value); - if (!value) { - mutex_lock ml(metadata_mu_); - run_metadata_.Clear(); - } -} - -} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h deleted file mode 100644 index d525d44fe4..0000000000 --- a/tensorflow/core/common_runtime/eager/context.h +++ /dev/null @@ -1,193 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ -#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ - -#include -#include -#include -#include -#include -#include -#include - -#include "tensorflow/core/common_runtime/device_factory.h" -#include "tensorflow/core/common_runtime/eager/eager_executor.h" -#include "tensorflow/core/common_runtime/eager/kernel_and_device.h" -#include "tensorflow/core/common_runtime/function.h" -#include "tensorflow/core/common_runtime/rendezvous_mgr.h" -#include "tensorflow/core/framework/rendezvous.h" -#include "tensorflow/core/lib/core/stringpiece.h" -#include "tensorflow/core/lib/gtl/inlined_vector.h" -#include "tensorflow/core/lib/gtl/map_util.h" -#include "tensorflow/core/lib/gtl/stl_util.h" -#include "tensorflow/core/platform/fingerprint.h" -#include "tensorflow/core/platform/mutex.h" -#include "tensorflow/core/platform/thread_annotations.h" -#include "tensorflow/core/public/session_options.h" -#include "tensorflow/core/public/version.h" - -namespace tensorflow { - -// Note: there's a copy enum in eager/c_api.h. It should be kept in sync. -enum ContextDevicePlacementPolicy { - // Running operations with input tensors on the wrong device will fail. When - // soft placement is enabled acts like TFE_DEVICE_PLACEMENT_SILENT. - DEVICE_PLACEMENT_EXPLICIT = 0, - // Copy the tensor to the right device but log a warning. - DEVICE_PLACEMENT_WARN = 1, - // Silently copy the tensor, which has a performance cost since the - // operation will be blocked till the copy completes. - DEVICE_PLACEMENT_SILENT = 2, - // Default placement policy which silently copies int32 tensors but not other - // dtypes. When soft placement is enabled acts like - // TFE_DEVICE_PLACEMENT_SILENT. - DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, -}; - -ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, ContextDevicePlacementPolicy original_policy); - -class EagerContext { - public: - explicit EagerContext(const SessionOptions& opts, - ContextDevicePlacementPolicy default_policy, bool async, - std::unique_ptr device_mgr, - Rendezvous* rendezvous); - - ~EagerContext(); - - // Returns the function library runtime for the given device. - FunctionLibraryRuntime* func_lib(Device* d) const { - return pflr_->GetFLR(d->name()); - } - - // True if running in asynchronous mode. - bool Async() const; - - EagerExecutor* Executor() { return &executor_; } - - // Sets whether this thread should run in synchronous or asynchronous mode. - Status SetAsyncForThread(bool async); - - // TODO(apassos) make this return a constant reference - gtl::FlatMap* device_map() { - return &devices_map_; - } - - // TODO(apassos) make this return a constant reference - std::vector* devices() { return &devices_; } - - // Clears the kernel caches. - void ClearCaches(); - - // Sets the device placement policy for the current thread. - void SetThreadLocalDevicePlacementPolicy(ContextDevicePlacementPolicy policy); - - // Returns the device placement policy for the current thread. - ContextDevicePlacementPolicy GetDevicePlacementPolicy(); - - Status AsyncWait() { return executor_.WaitForAllPendingNodes(); } - - Status GetStatus() { return executor_.status(); } - - void ClearAsyncError() { executor_.ClearError(); } - - bool FindFunctionByName(const string& name); - - Status FindDeviceByName(const string& name, Device** result); - - Device* HostCPU() { return devices_[0]; } - - bool SoftPlacement() { return soft_placement_; } - - uint64 NextId() { return executor_.NextId(); } - - void ExecutorAdd(EagerNode* node) { executor_.Add(node); } - - Status AddFunctionDef(const FunctionDef& fdef); - - KernelAndDevice* GetCachedKernel(Fprint128 cache_key); - - void AddKernelToCache(Fprint128 cache_key, KernelAndDevice* kernel); - - bool LogDevicePlacement() { return log_device_placement_; } - - Rendezvous* GetRendezvous() { return rendezvous_; } - - mutex* FunctionsMu() { return &functions_mu_; } - - tensorflow::DeviceMgr* device_mgr() { return device_manager_.get(); } - - // TODO(apassos) remove the need for this - void ReleaseDeviceMgr() { device_manager_.release(); } - - // TODO(apassos) clean up RunMetadata storage. - mutex* MetadataMu() { return &metadata_mu_; } - bool ShouldStoreMetadata() { return should_store_metadata_.load(); } - void SetShouldStoreMetadata(bool value); - RunMetadata* RunMetadataProto() { return &run_metadata_; } - - FunctionLibraryDefinition* FuncLibDef() { return &func_lib_def_; } - - private: - const bool soft_placement_; - const ContextDevicePlacementPolicy policy_; - - // Note: we cannot use C++11 thread_local here as there is no concept of a - // thread-local-object-local variable in C++11. - mutex policy_map_mu_; - std::unordered_map - thread_local_policies_ GUARDED_BY(policy_map_mu_); - - std::unique_ptr device_manager_; - // Devices owned by device_manager - std::vector devices_; - // All devices are not owned. - gtl::FlatMap devices_map_; - Rendezvous* const rendezvous_; - - mutex functions_mu_; - FunctionLibraryDefinition func_lib_def_ GUARDED_BY(functions_mu_){ - OpRegistry::Global(), {}}; - - // One FunctionLibraryRuntime per device. - // func_libs[i] is the FunctionLibraryRuntime corresponding to - // session->devices[i]. - const std::unique_ptr pflr_; - - mutex cache_mu_; - std::unordered_map kernel_cache_ - GUARDED_BY(cache_mu_); - - // Whether we should compute RunMetadata. - std::atomic should_store_metadata_{false}; - mutex metadata_mu_; - RunMetadata run_metadata_ GUARDED_BY(metadata_mu_); - const bool log_device_placement_; - // EagerExecutor for async execution. - EagerExecutor executor_; - - // True if the default value for execution mode is async. Note that this value - // can be overridden per thread based on `thread_local_async` overrides. - const bool async_default_; - mutable mutex async_map_mu_; - std::unordered_map thread_local_async_ - GUARDED_BY(async_map_mu_); -}; - -} // namespace tensorflow - -#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ -- GitLab From 585fb74541ed914845eccd3da4b1a2c94a99779e Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 22 Mar 2018 00:26:31 -0700 Subject: [PATCH 060/906] Minor style improvement in TFLite interpreter_test.py PiperOrigin-RevId: 190027161 --- .../contrib/lite/python/interpreter_test.py | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tensorflow/contrib/lite/python/interpreter_test.py b/tensorflow/contrib/lite/python/interpreter_test.py index bf124410f3..cd2386f526 100644 --- a/tensorflow/contrib/lite/python/interpreter_test.py +++ b/tensorflow/contrib/lite/python/interpreter_test.py @@ -61,30 +61,31 @@ class InterpreterTest(test_util.TensorFlowTestCase): 'testdata/permute_uint8.tflite') with io.open(model_path, 'rb') as model_file: data = model_file.read() - interpreter = interpreter_wrapper.Interpreter(model_content=data) - interpreter.allocate_tensors() - - input_details = interpreter.get_input_details() - self.assertEqual(1, len(input_details)) - self.assertEqual('input', input_details[0]['name']) - self.assertEqual(np.uint8, input_details[0]['dtype']) - self.assertTrue(([1, 4] == input_details[0]['shape']).all()) - self.assertEqual((1.0, 0), input_details[0]['quantization']) - - output_details = interpreter.get_output_details() - self.assertEqual(1, len(output_details)) - self.assertEqual('output', output_details[0]['name']) - self.assertEqual(np.uint8, output_details[0]['dtype']) - self.assertTrue(([1, 4] == output_details[0]['shape']).all()) - self.assertEqual((1.0, 0), output_details[0]['quantization']) - - test_input = np.array([[1, 2, 3, 4]], dtype=np.uint8) - expected_output = np.array([[4, 3, 2, 1]], dtype=np.uint8) - interpreter.set_tensor(input_details[0]['index'], test_input) - interpreter.invoke() - - output_data = interpreter.get_tensor(output_details[0]['index']) - self.assertTrue((expected_output == output_data).all()) + + interpreter = interpreter_wrapper.Interpreter(model_content=data) + interpreter.allocate_tensors() + + input_details = interpreter.get_input_details() + self.assertEqual(1, len(input_details)) + self.assertEqual('input', input_details[0]['name']) + self.assertEqual(np.uint8, input_details[0]['dtype']) + self.assertTrue(([1, 4] == input_details[0]['shape']).all()) + self.assertEqual((1.0, 0), input_details[0]['quantization']) + + output_details = interpreter.get_output_details() + self.assertEqual(1, len(output_details)) + self.assertEqual('output', output_details[0]['name']) + self.assertEqual(np.uint8, output_details[0]['dtype']) + self.assertTrue(([1, 4] == output_details[0]['shape']).all()) + self.assertEqual((1.0, 0), output_details[0]['quantization']) + + test_input = np.array([[1, 2, 3, 4]], dtype=np.uint8) + expected_output = np.array([[4, 3, 2, 1]], dtype=np.uint8) + interpreter.set_tensor(input_details[0]['index'], test_input) + interpreter.invoke() + + output_data = interpreter.get_tensor(output_details[0]['index']) + self.assertTrue((expected_output == output_data).all()) if __name__ == '__main__': -- GitLab From 31adaf4361b9b65e382be9633c4d0517d77c29e5 Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 22 Mar 2018 00:26:33 -0700 Subject: [PATCH 061/906] TFLite: Ensure only 1 scale/zero_point is in QuantizationParam. PiperOrigin-RevId: 190027163 --- tensorflow/contrib/lite/model.cc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc index f7daa6fc9d..9c619f88e0 100644 --- a/tensorflow/contrib/lite/model.cc +++ b/tensorflow/contrib/lite/model.cc @@ -679,9 +679,27 @@ TfLiteStatus InterpreterBuilder::ParseTensors( // but we really only support one value for the whole tensor. // TODO(aselle): This breaks as well if these are nullptr's. // TODO(aselle): This assumes non per-channel quantization. - if (q_params->scale()) quantization.scale = q_params->scale()->Get(0); - if (q_params->zero_point()) + + if (q_params->scale()) { + if (q_params->scale()->size() != 1) { + error_reporter_->Report( + "QuantizationParam has %d scale values (only 1 is supported).", + q_params->scale()->size()); + return kTfLiteError; + } + quantization.scale = q_params->scale()->Get(0); + } + + if (q_params->zero_point()) { + if (q_params->zero_point()->size() != 1) { + error_reporter_->Report( + "QuantizationParam has %d zero_point values" + " (only 1 is supported).", + q_params->zero_point()->size()); + return kTfLiteError; + } quantization.zero_point = q_params->zero_point()->Get(0); + } } TfLiteType type; -- GitLab From ba97ee847d9baca0ac3b7eab5c6bad93e70a2882 Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Thu, 22 Mar 2018 00:48:30 -0700 Subject: [PATCH 062/906] Java: Release 1.7.0-rc1 PiperOrigin-RevId: 190028714 --- tensorflow/java/maven/libtensorflow/pom.xml | 2 +- tensorflow/java/maven/libtensorflow_jni/pom.xml | 2 +- tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml | 2 +- tensorflow/java/maven/pom.xml | 2 +- tensorflow/java/maven/proto/pom.xml | 2 +- tensorflow/java/maven/tensorflow/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/java/maven/libtensorflow/pom.xml b/tensorflow/java/maven/libtensorflow/pom.xml index 7f3a83b195..0b69a8cbe5 100644 --- a/tensorflow/java/maven/libtensorflow/pom.xml +++ b/tensorflow/java/maven/libtensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 ../ libtensorflow diff --git a/tensorflow/java/maven/libtensorflow_jni/pom.xml b/tensorflow/java/maven/libtensorflow_jni/pom.xml index cc436ff840..541876f7f5 100644 --- a/tensorflow/java/maven/libtensorflow_jni/pom.xml +++ b/tensorflow/java/maven/libtensorflow_jni/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 ../ libtensorflow_jni diff --git a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml index 47f678382a..d8933e5238 100644 --- a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml +++ b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 ../ libtensorflow_jni_gpu diff --git a/tensorflow/java/maven/pom.xml b/tensorflow/java/maven/pom.xml index 42d32810a2..6286fd73df 100644 --- a/tensorflow/java/maven/pom.xml +++ b/tensorflow/java/maven/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 pom https://www.tensorflow.org diff --git a/tensorflow/java/maven/proto/pom.xml b/tensorflow/java/maven/proto/pom.xml index 463893ce62..4e881f5a63 100644 --- a/tensorflow/java/maven/proto/pom.xml +++ b/tensorflow/java/maven/proto/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 ../ proto diff --git a/tensorflow/java/maven/tensorflow/pom.xml b/tensorflow/java/maven/tensorflow/pom.xml index 60e7f3c199..d512a7eda9 100644 --- a/tensorflow/java/maven/tensorflow/pom.xml +++ b/tensorflow/java/maven/tensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.7.0-rc0 + 1.7.0-rc1 ../ tensorflow -- GitLab From f9ccb89134d89469ae962bba832e78d1f116b96b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 04:50:42 -0700 Subject: [PATCH 063/906] Add a utility that converts call keyword arguments into dicts, in AST space. PiperOrigin-RevId: 190047495 --- tensorflow/contrib/py2tf/pyct/ast_util.py | 9 +++++++++ tensorflow/contrib/py2tf/pyct/ast_util_test.py | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tensorflow/contrib/py2tf/pyct/ast_util.py b/tensorflow/contrib/py2tf/pyct/ast_util.py index f916775b9c..6f7e656c26 100644 --- a/tensorflow/contrib/py2tf/pyct/ast_util.py +++ b/tensorflow/contrib/py2tf/pyct/ast_util.py @@ -94,3 +94,12 @@ def rename_symbols(node, name_map): elif isinstance(node, tuple): return tuple(renamer.visit(n) for n in node) return renamer.visit(node) + + +def keywords_to_dict(keywords): + keys = [] + values = [] + for kw in keywords: + keys.append(gast.Str(kw.arg)) + values.append(kw.value) + return gast.Dict(keys=keys, values=values) diff --git a/tensorflow/contrib/py2tf/pyct/ast_util_test.py b/tensorflow/contrib/py2tf/pyct/ast_util_test.py index a871ccad6f..8d123679e3 100644 --- a/tensorflow/contrib/py2tf/pyct/ast_util_test.py +++ b/tensorflow/contrib/py2tf/pyct/ast_util_test.py @@ -21,6 +21,8 @@ from __future__ import print_function import ast from tensorflow.contrib.py2tf.pyct import ast_util +from tensorflow.contrib.py2tf.pyct import compiler +from tensorflow.contrib.py2tf.pyct import parser from tensorflow.contrib.py2tf.pyct import qual_names from tensorflow.python.platform import test @@ -74,6 +76,17 @@ class AstUtilTest(test.TestCase): self.assertFalse(ret is new_node.body[0]) self.assertFalse(hasattr(new_node.body[0], '__foo')) + def test_keywords_to_dict(self): + keywords = parser.parse_expression('f(a=b, c=1, d=\'e\')').keywords + d = ast_util.keywords_to_dict(keywords) + # Make sure we generate a usable dict node by attaching it to a variable and + # compiling everything. + output = parser.parse_str('b = 3') + output.body += (ast.Assign([ast.Name(id='d', ctx=ast.Store())], d),) + result, _ = compiler.ast_to_object(output) + self.assertDictEqual(result.d, {'a': 3, 'c': 1, 'd': 'e'}) + print(d) + if __name__ == '__main__': test.main() -- GitLab From 8cd562c55cb5fa172345f0de0376d9666b2326b4 Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Thu, 22 Mar 2018 19:55:34 +0800 Subject: [PATCH 064/906] Fix the inconsistency in the accepted shape/data_format of Input Tensor to Conv2D in documentation (#17893) --- tensorflow/docs_src/tutorials/layers.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/docs_src/tutorials/layers.md b/tensorflow/docs_src/tutorials/layers.md index 9b17d0d4d5..aeb746f29c 100644 --- a/tensorflow/docs_src/tutorials/layers.md +++ b/tensorflow/docs_src/tutorials/layers.md @@ -198,9 +198,9 @@ Classifier"](#training_and_evaluating_the_cnn_mnist_classifier). ### Input Layer The methods in the `layers` module for creating convolutional and pooling layers -for two-dimensional image data expect input tensors to have a shape of -[batch_size, image_width, image_height, -channels], defined as follows: +for two-dimensional image data expect input tensors to have a `channels_last` shape of +[batch_size, image_height, image_width, channels] +or a `channels_first` shape of [batch_size, channels, image_height, image_width], defined as follows: * _`batch_size`_. Size of the subset of examples to use when performing gradient descent during training. -- GitLab From 9e651e4571f7b7c2d32bdafe43cc4ced9bb0c750 Mon Sep 17 00:00:00 2001 From: Ilya Biryukov Date: Thu, 22 Mar 2018 05:33:42 -0700 Subject: [PATCH 065/906] Allow to download clang and use clang for CPU builds. Previously we only allowed to download clang when doing GPU builds. The added skylark files use bazel's autoconf scripts, which were only added in 0.10.0. To provide nice error message for older versions of bazel (i.e. 'version is less than 0.10' vs 'can't load @bazel_tools/cpp/...'), we move the bazel version check into WORKSPACE file from workspace.bzl. PiperOrigin-RevId: 190050798 --- WORKSPACE | 6 +++ configure.py | 26 ++++----- tensorflow/version_check.bzl | 48 +++++++++++++++++ tensorflow/workspace.bzl | 53 ++----------------- third_party/clang_toolchain/BUILD | 0 .../clang_toolchain/cc_configure_clang.bzl | 27 ++++++++++ .../download_clang.bzl | 0 third_party/gpus/cuda_configure.bzl | 2 +- third_party/mkl_dnn/mkldnn.BUILD | 2 +- tools/bazel.rc | 5 +- 10 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 tensorflow/version_check.bzl create mode 100644 third_party/clang_toolchain/BUILD create mode 100644 third_party/clang_toolchain/cc_configure_clang.bzl rename third_party/{gpus => clang_toolchain}/download_clang.bzl (100%) diff --git a/WORKSPACE b/WORKSPACE index 1e38a9a8cd..11c5cdb207 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -14,6 +14,12 @@ load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories") closure_repositories() +# We must check the bazel version before trying to parse any other BUILD +# files, in case the parsing of those build files depends on the bazel +# version we require here. +load("//tensorflow:version_check.bzl", "check_bazel_version_at_least") +check_bazel_version_at_least("0.10.0") + load("//tensorflow:workspace.bzl", "tf_workspace") # Uncomment and update the paths in these entries to build the Android demo. diff --git a/configure.py b/configure.py index 7d61c2e5e3..ea732c64e2 100644 --- a/configure.py +++ b/configure.py @@ -524,7 +524,7 @@ def set_tf_cuda_clang(environ_cp): def set_tf_download_clang(environ_cp): """Set TF_DOWNLOAD_CLANG action_env.""" - question = 'Do you want to download a fresh release of clang? (Experimental)' + question = 'Do you wish to download a fresh release of clang? (Experimental)' yes_reply = 'Clang will be downloaded and used to compile tensorflow.' no_reply = 'Clang will not be downloaded.' set_action_env_var( @@ -1380,7 +1380,7 @@ def main(): # environment variables. environ_cp = dict(os.environ) - check_bazel_version('0.5.4') + check_bazel_version('0.10.0') reset_tf_configure_bazelrc(args.workspace) cleanup_makefile() @@ -1397,6 +1397,9 @@ def main(): environ_cp['TF_NEED_OPENCL'] = '0' environ_cp['TF_CUDA_CLANG'] = '0' environ_cp['TF_NEED_TENSORRT'] = '0' + # TODO(ibiryukov): Investigate using clang as a cpu or cuda compiler on + # Windows. + environ_cp['TF_DOWNLOAD_CLANG'] = '0' if is_macos(): environ_cp['TF_NEED_JEMALLOC'] = '0' @@ -1444,16 +1447,8 @@ def main(): set_tf_cuda_clang(environ_cp) if environ_cp.get('TF_CUDA_CLANG') == '1': - if not is_windows(): - # Ask if we want to download clang release while building. - set_tf_download_clang(environ_cp) - else: - # We use bazel's generated crosstool on Windows and there is no - # way to provide downloaded toolchain for that yet. - # TODO(ibiryukov): Investigate using clang as a cuda compiler on - # Windows. - environ_cp['TF_DOWNLOAD_CLANG'] = '0' - + # Ask whether we should download the clang toolchain. + set_tf_download_clang(environ_cp) if environ_cp.get('TF_DOWNLOAD_CLANG') != '1': # Set up which clang we should use as the cuda / host compiler. set_clang_cuda_compiler_path(environ_cp) @@ -1463,6 +1458,13 @@ def main(): if not is_windows(): set_gcc_host_compiler_path(environ_cp) set_other_cuda_vars(environ_cp) + else: + # CUDA not required. Ask whether we should download the clang toolchain and + # use it for the CPU build. + set_tf_download_clang(environ_cp) + if environ_cp.get('TF_DOWNLOAD_CLANG') == '1': + write_to_bazelrc('build --config=download_clang') + write_to_bazelrc('test --config=download_clang') set_build_var(environ_cp, 'TF_NEED_MPI', 'MPI', 'with_mpi_support', False) if environ_cp.get('TF_NEED_MPI') == '1': diff --git a/tensorflow/version_check.bzl b/tensorflow/version_check.bzl new file mode 100644 index 0000000000..79e721dab4 --- /dev/null +++ b/tensorflow/version_check.bzl @@ -0,0 +1,48 @@ +""" Helpers to check minimum version of bazel.""" + +def _extract_version_number(bazel_version): + """Extracts the semantic version number from a version string + + Args: + bazel_version: the version string that begins with the semantic version + e.g. "1.2.3rc1 abc1234" where "abc1234" is a commit hash. + + Returns: + The semantic version string, like "1.2.3". + """ + for i in range(len(bazel_version)): + c = bazel_version[i] + if not (c.isdigit() or c == "."): + return bazel_version[:i] + return bazel_version + +# Parse the bazel version string from `native.bazel_version`. +# e.g. +# "0.10.0rc1 abc123d" => (0, 10, 0) +# "0.3.0" => (0, 3, 0) +def _parse_bazel_version(bazel_version): + """Parses a version string into a 3-tuple of ints + + int tuples can be compared directly using binary operators (<, >). + + Args: + bazel_version: the Bazel version string + + Returns: + An int 3-tuple of a (major, minor, patch) version. + """ + + version = _extract_version_number(bazel_version) + return tuple([int(n) for n in version.split(".")]) + +def check_bazel_version_at_least(minimum_bazel_version): + if "bazel_version" not in dir(native): + fail("\nCurrent Bazel version is lower than 0.2.1, expected at least %s\n" % minimum_bazel_version) + elif not native.bazel_version: + print("\nCurrent Bazel is not a release version, cannot check for compatibility.") + print("Make sure that you are running at least Bazel %s.\n" % minimum_bazel_version) + return + + if _parse_bazel_version(native.bazel_version) < _parse_bazel_version(minimum_bazel_version): + fail("\nCurrent Bazel version is {}, expected at least {}\n".format( + native.bazel_version, minimum_bazel_version)) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 675acbe5f6..ebb9e9412f 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -10,65 +10,18 @@ load("//third_party/sycl:sycl_configure.bzl", "sycl_configure") load("//third_party/toolchains/clang6:repo.bzl", "clang6_configure") load("//third_party/toolchains/cpus/arm:arm_compiler_configure.bzl", "arm_compiler_configure") load("//third_party:repo.bzl", "tf_http_archive") +load("//third_party/clang_toolchain:cc_configure_clang.bzl", "cc_download_clang_toolchain") load("@io_bazel_rules_closure//closure/private:java_import_external.bzl", "java_import_external") load("@io_bazel_rules_closure//closure:defs.bzl", "filegroup_external") -def _extract_version_number(bazel_version): - """Extracts the semantic version number from a version string - - Args: - bazel_version: the version string that begins with the semantic version - e.g. "1.2.3rc1 abc1234" where "abc1234" is a commit hash. - - Returns: - The semantic version string, like "1.2.3". - """ - for i in range(len(bazel_version)): - c = bazel_version[i] - if not (c.isdigit() or c == "."): - return bazel_version[:i] - return bazel_version - -# Parse the bazel version string from `native.bazel_version`. -# e.g. -# "0.10.0rc1 abc123d" => (0, 10, 0) -# "0.3.0" => (0, 3, 0) -def _parse_bazel_version(bazel_version): - """Parses a version string into a 3-tuple of ints - - int tuples can be compared directly using binary operators (<, >). - - Args: - bazel_version: the Bazel version string - - Returns: - An int 3-tuple of a (major, minor, patch) version. - """ - - version = _extract_version_number(bazel_version) - return tuple([int(n) for n in version.split(".")]) - -def check_bazel_version_at_least(minimum_bazel_version): - if "bazel_version" not in dir(native): - fail("\nCurrent Bazel version is lower than 0.2.1, expected at least %s\n" % minimum_bazel_version) - elif not native.bazel_version: - print("\nCurrent Bazel is not a release version, cannot check for compatibility.") - print("Make sure that you are running at least Bazel %s.\n" % minimum_bazel_version) - return - - if _parse_bazel_version(native.bazel_version) < _parse_bazel_version(minimum_bazel_version): - fail("\nCurrent Bazel version is {}, expected at least {}\n".format( - native.bazel_version, minimum_bazel_version)) # If TensorFlow is linked as a submodule. # path_prefix is no longer used. # tf_repo_name is thought to be under consideration. def tf_workspace(path_prefix="", tf_repo_name=""): - # We must check the bazel version before trying to parse any other BUILD - # files, in case the parsing of those build files depends on the bazel - # version we require here. - check_bazel_version_at_least("0.5.4") + # Note that we check the minimum bazel version in WORKSPACE. clang6_configure(name="local_config_clang6") + cc_download_clang_toolchain(name="local_config_download_clang") cuda_configure(name="local_config_cuda") tensorrt_configure(name="local_config_tensorrt") git_configure(name="local_config_git") diff --git a/third_party/clang_toolchain/BUILD b/third_party/clang_toolchain/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/clang_toolchain/cc_configure_clang.bzl b/third_party/clang_toolchain/cc_configure_clang.bzl new file mode 100644 index 0000000000..1181110ea9 --- /dev/null +++ b/third_party/clang_toolchain/cc_configure_clang.bzl @@ -0,0 +1,27 @@ +""" Downloads clang and configures the crosstool using bazel's autoconf.""" + +load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") +load(":download_clang.bzl", "download_clang") + +_TF_DOWNLOAD_CLANG = "TF_DOWNLOAD_CLANG" +_TF_NEED_CUDA = "TF_NEED_CUDA" + +def _cc_clang_autoconf(repo_ctx): + if repo_ctx.os.environ.get(_TF_DOWNLOAD_CLANG) != "1": + return + if repo_ctx.os.environ.get(_TF_NEED_CUDA) == "1": + # Clang is handled separately for CUDA configs. + # See cuda_configure.bzl for more details. + return + + download_clang(repo_ctx, out_folder='extra_tools') + overriden_tools = {'gcc': 'extra_tools/bin/clang'} + cc_autoconf_impl(repo_ctx, overriden_tools) + +cc_download_clang_toolchain = repository_rule( + environ = [ + _TF_DOWNLOAD_CLANG, + _TF_NEED_CUDA, + ], + implementation = _cc_clang_autoconf, +) diff --git a/third_party/gpus/download_clang.bzl b/third_party/clang_toolchain/download_clang.bzl similarity index 100% rename from third_party/gpus/download_clang.bzl rename to third_party/clang_toolchain/download_clang.bzl diff --git a/third_party/gpus/cuda_configure.bzl b/third_party/gpus/cuda_configure.bzl index 6c9c128db6..ede7e31897 100644 --- a/third_party/gpus/cuda_configure.bzl +++ b/third_party/gpus/cuda_configure.bzl @@ -96,7 +96,7 @@ NVVM_LIBDEVICE_PATHS = [ "share/cuda/", ] -load(":download_clang.bzl", "download_clang") +load("//third_party/clang_toolchain:download_clang.bzl", "download_clang") # TODO(dzc): Once these functions have been factored out of Bazel's # cc_configure.bzl, load them from @bazel_tools instead. diff --git a/third_party/mkl_dnn/mkldnn.BUILD b/third_party/mkl_dnn/mkldnn.BUILD index 752a0d8498..68f24aabae 100644 --- a/third_party/mkl_dnn/mkldnn.BUILD +++ b/third_party/mkl_dnn/mkldnn.BUILD @@ -4,7 +4,7 @@ config_setting( name = "clang_linux_x86_64", values = { "cpu": "k8", - "define": "using_cuda_clang=true", + "define": "using_clang=true", }, ) diff --git a/tools/bazel.rc b/tools/bazel.rc index 8b8c717561..1c1e6afb65 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -27,11 +27,14 @@ build --define framework_shared_object=true build:mkl --define=using_mkl=true build:mkl -c opt +build:download_clang --crosstool_top=@local_config_download_clang//:toolchain +build:download_clang --define=using_clang=true + build:cuda --crosstool_top=@local_config_cuda//crosstool:toolchain build:cuda --define=using_cuda=true --define=using_cuda_nvcc=true build:cuda_clang --crosstool_top=@local_config_cuda//crosstool:toolchain -build:cuda_clang --define=using_cuda=true --define=using_cuda_clang=true +build:cuda_clang --define=using_cuda=true --define=using_cuda_clang=true --define=using_clang=true build:win-cuda --define=using_cuda=true --define=using_cuda_nvcc=true -- GitLab From b559a319411e2d3f2a42f466c18737edd527bb10 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 05:46:07 -0700 Subject: [PATCH 066/906] Update file due to changes in Bazel (PACKAGE_NAME is deprecated) PiperOrigin-RevId: 190051589 --- tensorflow/compiler/aot/tfcompile.bzl | 2 +- tensorflow/tensorflow.bzl | 4 ++-- tensorflow/tools/test/performance.bzl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/aot/tfcompile.bzl b/tensorflow/compiler/aot/tfcompile.bzl index 9dff1be09f..3a877c5337 100644 --- a/tensorflow/compiler/aot/tfcompile.bzl +++ b/tensorflow/compiler/aot/tfcompile.bzl @@ -132,7 +132,7 @@ def tf_library(name, graph, config, header_file = name + ".h" metadata_object_file = name + "_tfcompile_metadata.o" function_object_file = name + "_tfcompile_function.o" - ep = ("__" + PACKAGE_NAME + "__" + name).replace("/", "_") + ep = ("__" + native.package_name() + "__" + name).replace("/", "_") if type(tfcompile_flags) == type(""): flags = tfcompile_flags else: diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 9b0db8a112..2d3cb415fe 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -34,7 +34,7 @@ def src_to_test_name(src): return src.replace("/", "_").split(".")[0] def full_path(relative_paths): - return [PACKAGE_NAME + "/" + relative for relative in relative_paths] + return [native.package_name() + "/" + relative for relative in relative_paths] # List of proto files for android builds def tf_android_core_proto_sources(core_proto_sources_relative): @@ -265,7 +265,7 @@ def _rpath_linkopts(name): # deployed. Other shared object dependencies (e.g. shared between contrib/ # ops) are picked up as long as they are in either the same or a parent # directory in the tensorflow/ tree. - levels_to_root = PACKAGE_NAME.count("/") + name.count("/") + levels_to_root = native.package_name().count("/") + name.count("/") return select({ clean_dep("//tensorflow:darwin"): [ "-Wl,%s" % (_make_search_paths("@loader_path", levels_to_root),), diff --git a/tensorflow/tools/test/performance.bzl b/tensorflow/tools/test/performance.bzl index cee53dd5b6..3486871080 100644 --- a/tensorflow/tools/test/performance.bzl +++ b/tensorflow/tools/test/performance.bzl @@ -31,7 +31,7 @@ def tf_cc_logged_benchmark( size = "large", srcs = ["//tensorflow/tools/test:run_and_gather_logs"], args = [ - "--name=//%s:%s" % (PACKAGE_NAME, name), + "--name=//%s:%s" % (native.package_name(), name), "--test_name=" + target, "--test_args=--benchmarks=%s" % benchmarks, "--benchmark_type=%s" % benchmark_type, -- GitLab From cae614a3300b3befae52d0e076c708450a93d820 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Thu, 22 Mar 2018 05:50:56 -0700 Subject: [PATCH 067/906] Automatically insert api-links. If the contents of a pair of back-ticks match a public api symbol name insert a link. PiperOrigin-RevId: 190051941 --- tensorflow/tools/docs/parser.py | 48 +++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index e758229535..d2a63ecc49 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -34,7 +34,11 @@ from tensorflow.python.util import tf_inspect # A regular expression capturing a python identifier. -IDENTIFIER_RE = '[a-zA-Z_][a-zA-Z0-9_]*' +IDENTIFIER_RE = r'[a-zA-Z_]\w*' + + +class TFDocsError(Exception): + pass class _Errors(object): @@ -118,6 +122,8 @@ SYMBOL_REFERENCE_RE = re.compile( """, flags=re.VERBOSE) +AUTO_REFERENCE_RE = re.compile(r'`([a-zA-Z0-9_.]+?)`') + class ReferenceResolver(object): """Class for replacing @{...} references with Markdown links. @@ -240,10 +246,25 @@ class ReferenceResolver(object): Returns: `string`, with "@{symbol}" references replaced by Markdown links. """ - def one_ref(match): - return self._one_ref(match, relative_path_to_root) - return re.sub(SYMBOL_REFERENCE_RE, one_ref, string) + def strict_one_ref(match): + try: + return self._one_ref(match, relative_path_to_root) + except TFDocsError as e: + self.add_error(e.message) + return 'BAD_LINK' + + string = re.sub(SYMBOL_REFERENCE_RE, strict_one_ref, string) + + def sloppy_one_ref(match): + try: + return self._one_ref(match, relative_path_to_root) + except TFDocsError: + return match.group(0) + + string = re.sub(AUTO_REFERENCE_RE, sloppy_one_ref, string) + + return string def python_link(self, link_text, ref_full_name, relative_path_to_root, code_ref=True): @@ -307,14 +328,14 @@ class ReferenceResolver(object): Raises: RuntimeError: If `ref_full_name` is not documented. + TFDocsError: If the @{} syntax cannot be decoded. """ master_name = self._duplicate_of.get(ref_full_name, ref_full_name) # Check whether this link exists if master_name not in self._all_names: - message = 'Cannot make link to "%s": Not in index.' % master_name - self.add_error(message) - return 'BROKEN_LINK' + raise TFDocsError( + 'Cannot make link to "%s": Not in index.' % master_name) # If this is a member of a class, link to the class page with an anchor. ref_path = None @@ -369,8 +390,8 @@ class ReferenceResolver(object): code_ref=not manual_link_text) # Error! - self.add_error('Did not understand "%s"' % match.group(0)) - return 'BROKEN_LINK' + raise TFDocsError('Did not understand "%s"' % match.group(0), + 'BROKEN_LINK') def _doc_link(self, string, link_text, manual_link_text, relative_path_to_root): @@ -395,11 +416,10 @@ class ReferenceResolver(object): return self._doc_missing(string, hash_tag, link_text, manual_link_text, relative_path_to_root) - def _doc_missing(self, string, unused_hash_tag, link_text, + def _doc_missing(self, string, unused_hash_tag, unused_link_text, unused_manual_link_text, unused_relative_path_to_root): """Generate an error for unrecognized @{$...} references.""" - self.add_error('Unknown Document "%s"' % string) - return link_text + raise TFDocsError('Unknown Document "%s"' % string) def _cc_link(self, string, link_text, unused_manual_link_text, relative_path_to_root): @@ -416,8 +436,8 @@ class ReferenceResolver(object): elif string == 'tensorflow::ops::Const': ret = 'namespace/tensorflow/ops.md#const' else: - self.add_error('C++ reference not understood: "%s"' % string) - return 'TODO_C++:%s' % string + raise TFDocsError('C++ reference not understood: "%s"' % string) + # relative_path_to_root gets you to api_docs/python, we go from there # to api_docs/cc, and then add ret. cc_relative_path = os.path.normpath(os.path.join( -- GitLab From 69dc403c97f273b750d5927ec1ed26613d90f3ad Mon Sep 17 00:00:00 2001 From: cbockman Date: Thu, 22 Mar 2018 06:42:39 -0700 Subject: [PATCH 068/906] spelling fix (#17911) --- tensorflow/contrib/data/python/ops/grouping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/data/python/ops/grouping.py b/tensorflow/contrib/data/python/ops/grouping.py index a19be22254..ae10d2eb22 100644 --- a/tensorflow/contrib/data/python/ops/grouping.py +++ b/tensorflow/contrib/data/python/ops/grouping.py @@ -42,7 +42,7 @@ def group_by_window(key_func, This transformation maps each consecutive element in a dataset to a key using `key_func` and groups the elements by key. It then applies `reduce_func` to at most `window_size_func(key)` elements matching the same - key. All execpt the final window for each key will contain + key. All except the final window for each key will contain `window_size_func(key)` elements; the final window may be smaller. You may provide either a constant `window_size` or a window size determined by -- GitLab From 4d5c139fbb831684e58b3875cd253a15c742362d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 08:19:21 -0700 Subject: [PATCH 069/906] Fix QN for Calls. PiperOrigin-RevId: 190067548 --- tensorflow/contrib/py2tf/pyct/qual_names.py | 20 +++++++------------ .../contrib/py2tf/pyct/qual_names_test.py | 18 +++++++++++++++++ .../py2tf/pyct/static_analysis/activity.py | 4 ++++ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/tensorflow/contrib/py2tf/pyct/qual_names.py b/tensorflow/contrib/py2tf/pyct/qual_names.py index 6bcbaeb2ae..7dec13db92 100644 --- a/tensorflow/contrib/py2tf/pyct/qual_names.py +++ b/tensorflow/contrib/py2tf/pyct/qual_names.py @@ -169,14 +169,6 @@ class QnResolver(gast.NodeTransformer): Note: Not using NodeAnnos to avoid circular dependencies. """ - def visit_Call(self, node): - node = self.generic_visit(node) - # This helps treat the following cases uniformly: - # a = b[i] - # a = b()[i] - anno.copyanno(node.func, node, anno.Basic.QN) - return node - def visit_Name(self, node): node = self.generic_visit(node) anno.setanno(node, anno.Basic.QN, QN(node.id)) @@ -184,8 +176,9 @@ class QnResolver(gast.NodeTransformer): def visit_Attribute(self, node): node = self.generic_visit(node) - anno.setanno(node, anno.Basic.QN, - QN(anno.getanno(node.value, anno.Basic.QN), attr=node.attr)) + if anno.hasanno(node.value, anno.Basic.QN): + anno.setanno(node, anno.Basic.QN, + QN(anno.getanno(node.value, anno.Basic.QN), attr=node.attr)) return node def visit_Subscript(self, node): @@ -201,9 +194,10 @@ class QnResolver(gast.NodeTransformer): subscript = QN(StringLiteral(s.value.s)) else: subscript = anno.getanno(node.slice.value, anno.Basic.QN) - anno.setanno(node, anno.Basic.QN, - QN(anno.getanno(node.value, anno.Basic.QN), - subscript=subscript)) + if anno.hasanno(node.value, anno.Basic.QN): + anno.setanno(node, anno.Basic.QN, + QN(anno.getanno(node.value, anno.Basic.QN), + subscript=subscript)) return node diff --git a/tensorflow/contrib/py2tf/pyct/qual_names_test.py b/tensorflow/contrib/py2tf/pyct/qual_names_test.py index f2cd8e98f0..6583fa243b 100644 --- a/tensorflow/contrib/py2tf/pyct/qual_names_test.py +++ b/tensorflow/contrib/py2tf/pyct/qual_names_test.py @@ -208,6 +208,24 @@ class QNResolverTest(test.TestCase): self.assertQNStringIs(nodes[8], 'a.b[c[d]].e.f') self.assertQNStringIs(nodes[9], 'a.b[c[d.e.f].g].h') + def test_function_calls(self): + samples = """ + a.b + a.b() + a().b + z[i] + z[i]() + z()[i] + """ + nodes = resolve(parser.parse_str(textwrap.dedent(samples))) + nodes = tuple(n.value for n in nodes.body) + self.assertQNStringIs(nodes[0], 'a.b') + self.assertQNStringIs(nodes[1].func, 'a.b') + self.assertQNStringIs(nodes[2].value.func, 'a') + self.assertQNStringIs(nodes[3], 'z[i]') + self.assertQNStringIs(nodes[4].func, 'z[i]') + self.assertQNStringIs(nodes[5].value.func, 'z') + if __name__ == '__main__': test.main() diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py b/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py index 87fc8c979c..716672a53b 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py +++ b/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py @@ -171,6 +171,10 @@ class ActivityAnalizer(transformer.Base): self._in_return_statement = False def _track_symbol(self, node): + # This can happen when we have an attribute (or subscript) on a function + # call. Example: a().b + if not anno.hasanno(node, anno.Basic.QN): + return qn = anno.getanno(node, anno.Basic.QN) if isinstance(node.ctx, gast.Store): -- GitLab From 4deaf50fd8bb10aa2c96662a106f201b281f57ee Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 08:51:43 -0700 Subject: [PATCH 070/906] Methods to work with symbolic tensor shapes. PiperOrigin-RevId: 190071400 --- tensorflow/core/grappler/optimizers/BUILD | 30 +++ .../grappler/optimizers/symbolic_shapes.cc | 177 ++++++++++++++++++ .../grappler/optimizers/symbolic_shapes.h | 60 ++++++ .../optimizers/symbolic_shapes_test.cc | 95 ++++++++++ 4 files changed, 362 insertions(+) create mode 100644 tensorflow/core/grappler/optimizers/symbolic_shapes.cc create mode 100644 tensorflow/core/grappler/optimizers/symbolic_shapes.h create mode 100644 tensorflow/core/grappler/optimizers/symbolic_shapes_test.cc diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 96ea8f7a83..ac29edd213 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -5,6 +5,12 @@ load("//tensorflow:tensorflow.bzl", "tf_cc_test_gpu") load("//tensorflow:tensorflow.bzl", "tf_kernel_library") load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda") +# Platform specific build config +load( + "//tensorflow/core:platform/default/build_config.bzl", + "tf_protos_grappler", +) + filegroup( name = "all_files", srcs = glob( @@ -586,3 +592,27 @@ tf_cc_test( "//tensorflow/core/grappler/utils:grappler_test", ], ) + +cc_library( + name = "symbolic_shapes", + srcs = ["symbolic_shapes.cc"], + hdrs = ["symbolic_shapes.h"], + visibility = ["//visibility:public"], + deps = [ + "//tensorflow/core:framework", + "//tensorflow/core:protos_all_cc", + ] + tf_protos_grappler(), +) + +tf_cc_test( + name = "symbolic_shapes_test", + srcs = ["symbolic_shapes_test.cc"], + deps = [ + ":symbolic_shapes", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) diff --git a/tensorflow/core/grappler/optimizers/symbolic_shapes.cc b/tensorflow/core/grappler/optimizers/symbolic_shapes.cc new file mode 100644 index 0000000000..cfca2dc0d3 --- /dev/null +++ b/tensorflow/core/grappler/optimizers/symbolic_shapes.cc @@ -0,0 +1,177 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/grappler/optimizers/symbolic_shapes.h" +#include "tensorflow/core/util/bcast.h" + +namespace tensorflow { +namespace grappler { +namespace { + +BCast::Vec ShapeDims(const TensorShapeProto& shape) { + BCast::Vec dims; + dims.reserve(shape.dim_size()); + for (int i = 0; i < shape.dim_size(); ++i) + dims.push_back(shape.dim(i).size()); + return dims; +} + +} // namespace + +bool IsKnown(const TensorShapeProto::Dim& dim) { return dim.size() >= 0; } + +bool IsKnownSymbolically(const TensorShapeProto::Dim& dim) { + return dim.size() <= -2; +} + +bool IsUnknown(const TensorShapeProto::Dim& dim) { return dim.size() == -1; } + +bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape) { + return !shape.unknown_rank() && + std::all_of( + shape.dim().begin(), shape.dim().end(), + [](const TensorShapeProto::Dim& dim) { return !IsUnknown(dim); }); +} + +bool ShapeIsSymbolicallyDefined(const OpInfo::TensorProperties& properties) { + return ShapeIsSymbolicallyDefined(properties.shape()); +} + +bool ShapesSymbolicallyEqual(const TensorShapeProto& left, + const TensorShapeProto& right) { + if (left.unknown_rank() || right.unknown_rank() || + left.dim_size() != right.dim_size()) { + return false; + } + for (int i = 0; i < left.dim_size(); ++i) { + const auto& ldim = left.dim(i); + const auto& rdim = right.dim(i); + if (IsUnknown(ldim) || IsUnknown(rdim) || ldim.size() != rdim.size()) { + return false; + } + } + return true; +} + +bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right) { + return ShapesSymbolicallyEqual(left.shape(), right.shape()); +} + +bool ShapesBroadcastable(const TensorShapeProto& left, + const TensorShapeProto& right) { + if (!ShapeIsSymbolicallyDefined(left) || !ShapeIsSymbolicallyDefined(right)) { + return false; + } + BCast bcast(ShapeDims(left), ShapeDims(right), + /*fewer_dims_optimization*/ false); + return bcast.IsValid(); +} + +bool ShapesBroadcastable(const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right) { + return ShapesBroadcastable(left.shape(), right.shape()); +} + +bool CompareSymbolicallyShapedTensorSizes(const TensorShapeProto& left, + const TensorShapeProto& right) { + // if one of the ranks is unknown, it's impossible to compare tensor sizes + if (left.unknown_rank() || right.unknown_rank()) { + return false; + } + + // Tensor size, computed as a product of defined dimensions + int64 left_defined_size = 1; + int64 right_defined_size = 1; + + // Keep how many times each unknown dimension appeared on the left and right + std::unordered_map left_unknown_dims; + std::unordered_map right_unknown_dims; + + // Assign unique id to every unknown dimension (-1). We are going to + // assign positive ids, because negative values are already used by + // symbolic dimensions. + int64 unknown_dim_id = 1; + + // For each shape dimension update "defined tensor size", if shape is defined, + // or increment a counter for unknown dim. + auto process_dimensions = + [&unknown_dim_id](const TensorShapeProto& shape, int64* defined_size, + std::unordered_map* unknown_dims) { + for (int i = 0; i < shape.dim_size(); ++i) { + const auto& dim = shape.dim(i); + int64 dim_size = dim.size(); + if (dim_size > 0) { + *defined_size *= dim_size; + } else if (IsUnknown(dim)) { + ++(*unknown_dims)[unknown_dim_id++]; + } else if (IsKnownSymbolically(dim)) { + ++(*unknown_dims)[dim_size]; + } + } + }; + + process_dimensions(left, &left_defined_size, &left_unknown_dims); + process_dimensions(right, &right_defined_size, &right_unknown_dims); + + // Compute a union of unknown dimension ids appeared in both shapes + std::set unknown_dims; + for (const auto& el : left_unknown_dims) unknown_dims.insert(el.first); + for (const auto& el : right_unknown_dims) unknown_dims.insert(el.first); + + // Cancel unknown dimensions that appeared in both shapes + for (int64 unknown_dim : unknown_dims) { + int64 co_occurrence = std::min(left_unknown_dims[unknown_dim], + right_unknown_dims[unknown_dim]); + left_unknown_dims[unknown_dim] -= co_occurrence; + right_unknown_dims[unknown_dim] -= co_occurrence; + } + + // Count unbalanced unknown dimensions + int64 left_unbalanced_unknown_dims = 0; + int64 right_unbalanced_unknown_dims = 0; + for (const auto& el : left_unknown_dims) + left_unbalanced_unknown_dims += el.second; + for (const auto& el : right_unknown_dims) + right_unbalanced_unknown_dims += el.second; + + if (left_unbalanced_unknown_dims == 0 && right_unbalanced_unknown_dims == 0) { + // If unknown dimensions cancelled each other, compare tensor sizes + // represented by defined dimensions + return left_defined_size < right_defined_size; + } + + if (left_defined_size <= right_defined_size && + left_unbalanced_unknown_dims == 0 && right_unbalanced_unknown_dims > 0) { + // If size of a 'left" tensor computed from defined dimensions less or + // equal, and shape on the right has unbalanced unknown dimensions, we can + // guarantee that shape on the left is strictly smaller (assuming that + // unknown dimension size is larger than 1) + return true; + } + + // In every other case, assuming that unknown dimensions can be arbitrary + // large in size, we can't guarantee any ordering + return false; +} + +bool CompareSymbolicallyShapedTensorSizes( + const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right) { + return CompareSymbolicallyShapedTensorSizes(left.shape(), right.shape()); +} + +} // end namespace grappler +} // end namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/symbolic_shapes.h b/tensorflow/core/grappler/optimizers/symbolic_shapes.h new file mode 100644 index 0000000000..a9dcf44e23 --- /dev/null +++ b/tensorflow/core/grappler/optimizers/symbolic_shapes.h @@ -0,0 +1,60 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_SYMBOLIC_SHAPES_H_ +#define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_SYMBOLIC_SHAPES_H_ + +#include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/grappler/costs/op_performance_data.pb.h" + +namespace tensorflow { +namespace grappler { + +bool IsKnown(const TensorShapeProto::Dim& dim); +bool IsKnownSymbolically(const TensorShapeProto::Dim& dim); +bool IsUnknown(const TensorShapeProto::Dim& dim); + +// Shape is symbolically defined, if it has a known rank, and each dimension is +// known (dim_size >= 0), or is a symbolic dimension size (dim_size <= -2). +bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape); +bool ShapeIsSymbolicallyDefined(const OpInfo::TensorProperties& properties); + +// Shapes are symbolically equal, if they have the same rank, they are +// they are known or symbolically defined, and have matching dimensions. +bool ShapesSymbolicallyEqual(const TensorShapeProto& left, + const TensorShapeProto& right); +bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right); + +// Check if two shapes can be broadcasted to each other. Both shapes must be at +// least symbolically defined, and the have valid BCast instance. +bool ShapesBroadcastable(const TensorShapeProto& left, + const TensorShapeProto& right); +bool ShapesBroadcastable(const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right); + +// Return true if can prove, that tensor of size 'left' is smaller than tensor +// of size 'right'. Return false if it's larger or equal, or it's impossible to +// compare because of unknown dimensions, or mismatch in symbolic dimensions. +bool CompareSymbolicallyShapedTensorSizes(const TensorShapeProto& left, + const TensorShapeProto& right); +bool CompareSymbolicallyShapedTensorSizes( + const OpInfo::TensorProperties& left, + const OpInfo::TensorProperties& right); + +} // namespace grappler +} // end namespace tensorflow + +#endif // TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_SYMBOLIC_SHAPES_H_ diff --git a/tensorflow/core/grappler/optimizers/symbolic_shapes_test.cc b/tensorflow/core/grappler/optimizers/symbolic_shapes_test.cc new file mode 100644 index 0000000000..5ef9f65925 --- /dev/null +++ b/tensorflow/core/grappler/optimizers/symbolic_shapes_test.cc @@ -0,0 +1,95 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/grappler/optimizers/symbolic_shapes.h" +#include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace grappler { +namespace { + +class SymbolicShapesTest : public ::testing::Test { + protected: + TensorShapeProto MakeUnknown() { + TensorShapeProto shape; + shape.set_unknown_rank(true); + return shape; + } + + TensorShapeProto MakeShape(std::vector dims) { + TensorShapeProto shape; + for (int dim_size : dims) { + TensorShapeProto::Dim dim; + dim.set_size(dim_size); + *shape.add_dim() = dim; + } + return shape; + } +}; + +bool operator<(const TensorShapeProto& lhs, const TensorShapeProto& rhs) { + return CompareSymbolicallyShapedTensorSizes(lhs, rhs); +} + +TEST_F(SymbolicShapesTest, ShapeIsSymbolicallyDefined) { + EXPECT_FALSE(ShapeIsSymbolicallyDefined(MakeUnknown())); + EXPECT_FALSE(ShapeIsSymbolicallyDefined(MakeShape({-1, 2}))); + + EXPECT_TRUE(ShapeIsSymbolicallyDefined(MakeShape({1, 2}))); + EXPECT_TRUE(ShapeIsSymbolicallyDefined(MakeShape({-2, 2}))); +} + +TEST_F(SymbolicShapesTest, ShapesSymbolicallyEqual) { + EXPECT_FALSE(ShapesSymbolicallyEqual(MakeUnknown(), MakeUnknown())); + EXPECT_FALSE(ShapesSymbolicallyEqual(MakeShape({-1, 2}), MakeShape({-1, 2}))); + EXPECT_FALSE(ShapesSymbolicallyEqual(MakeShape({-2, 2}), MakeShape({-3, 2}))); + + EXPECT_TRUE(ShapesSymbolicallyEqual(MakeShape({1, 2}), MakeShape({1, 2}))); + EXPECT_TRUE(ShapesSymbolicallyEqual(MakeShape({-2, 2}), MakeShape({-2, 2}))); +} + +TEST_F(SymbolicShapesTest, ShapesBroadcastable) { + EXPECT_FALSE(ShapesBroadcastable(MakeUnknown(), MakeUnknown())); + EXPECT_FALSE(ShapesBroadcastable(MakeShape({-2}), MakeShape({1, -3}))); + EXPECT_FALSE(ShapesBroadcastable(MakeShape({-1, 2}), MakeShape({-1, 2}))); + EXPECT_FALSE(ShapesBroadcastable(MakeShape({-2, 2}), MakeShape({-3, 2}))); + EXPECT_FALSE(ShapesBroadcastable(MakeShape({-2, 4}), MakeShape({-2, 8}))); + + EXPECT_TRUE(ShapesBroadcastable(MakeShape({1, 2}), MakeShape({1, 2}))); + EXPECT_TRUE(ShapesBroadcastable(MakeShape({-2, 2}), MakeShape({-2, 2}))); + EXPECT_TRUE(ShapesBroadcastable(MakeShape({-2, 32}), MakeShape({-2, 1}))); + EXPECT_TRUE(ShapesBroadcastable(MakeShape({-2, 1}), MakeShape({1, -2}))); + EXPECT_TRUE(ShapesBroadcastable(MakeShape({-2, 1}), MakeShape({1, -3}))); + EXPECT_TRUE(ShapesBroadcastable(MakeShape({-3}), MakeShape({-2, -3}))); +} + +TEST_F(SymbolicShapesTest, CompareSymbolicallyShapedTensorSizes) { + EXPECT_TRUE(MakeShape({1, 1, 32}) < MakeShape({32, 32})); + EXPECT_TRUE(MakeShape({1, 32, 32}) < MakeShape({2048})); + EXPECT_TRUE(MakeShape({1, -2, 32}) < MakeShape({-2, 32, 32})); + EXPECT_TRUE(MakeShape({1, 32, 32}) < MakeShape({-2, 32, 32})); + EXPECT_TRUE(MakeShape({1, 32, 32}) < MakeShape({-1, 32, 32})); + EXPECT_TRUE(MakeShape({1, -2, 32}) < MakeShape({-2, -2, 32})); + + EXPECT_FALSE(MakeShape({1, -2, 32}) < MakeShape({-3, 32, 32})); + EXPECT_FALSE(MakeShape({1, -1, 32}) < MakeShape({1, -1, 32})); + EXPECT_FALSE(MakeShape({1, -1, 32}) < MakeShape({-1, -1, 32})); + EXPECT_FALSE(MakeShape({-1, -1, 32}) < MakeShape({1, -1, 32})); +} + +} // namespace +} // namespace grappler +} // namespace tensorflow -- GitLab From e92c2e0c957bc539fc24dffdceb96f4b3955bbee Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 09:14:07 -0700 Subject: [PATCH 071/906] Disable over-aggressive shape inference PiperOrigin-RevId: 190074445 --- tensorflow/core/ops/list_ops.cc | 4 ---- .../python/kernel_tests/list_ops_test.py | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/ops/list_ops.cc b/tensorflow/core/ops/list_ops.cc index 0c16abd369..cad617638f 100644 --- a/tensorflow/core/ops/list_ops.cc +++ b/tensorflow/core/ops/list_ops.cc @@ -135,10 +135,6 @@ REGISTER_OP("TensorListStack") } shape_inference::ShapeHandle ignored; TF_RETURN_IF_ERROR(c->Merge(s, list_shape_type.shape, &ignored)); - if (!c->FullyDefined(list_shape_type.shape)) { - return errors::InvalidArgument( - "Can only stack a list with fully defined shapes."); - } s = list_shape_type.shape; } int expected_num_elements = -1; diff --git a/tensorflow/python/kernel_tests/list_ops_test.py b/tensorflow/python/kernel_tests/list_ops_test.py index 8865e165fd..dbbed39c72 100644 --- a/tensorflow/python/kernel_tests/list_ops_test.py +++ b/tensorflow/python/kernel_tests/list_ops_test.py @@ -152,6 +152,28 @@ class ListOpsTest(test_util.TensorFlowTestCase): s1 = list_ops.tensor_list_stack(t1, element_dtype=dtypes.int32).eval() self.assertAllEqual(s1, [0, 1, 2, 3]) + def testGraphStackSwitchDtype(self): + with context.graph_mode(), self.test_session(): + list_ = list_ops.empty_tensor_list( + element_shape=constant_op.constant([], dtype=dtypes.int32), + element_dtype=dtypes.int32) + m = constant_op.constant([1, 2, 3], dtype=dtypes.float32) + + def body(list_, m): + list_ = control_flow_ops.cond( + math_ops.equal(list_ops.tensor_list_length(list_), 0), + lambda: list_ops.empty_tensor_list(m.shape, m.dtype), lambda: list_) + list_ = list_ops.tensor_list_push_back(list_, m) + return list_, m + + for _ in range(2): + list_, m = body(list_, m) + + s1 = list_ops.tensor_list_stack( + list_, element_dtype=dtypes.float32).eval() + np_s1 = np.array([[1, 2, 3], [1, 2, 3]], dtype=np.float32) + self.assertAllEqual(s1, np_s1) + def testGraphStackInLoopSwitchDtype(self): with context.graph_mode(), self.test_session(): t1 = list_ops.empty_tensor_list( -- GitLab From b6ad189b1a197e454ae527829a01f742d76ba2a2 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Thu, 22 Mar 2018 09:34:29 -0700 Subject: [PATCH 072/906] [XLA] Only overwrite the hlo_profiling flag when it's not enabled by default. This got broken in 504d103a405654f029e8902d97d4dd8f3aa07513 PiperOrigin-RevId: 190077360 --- .../compiler/xla/client/executable_build_options.cc | 4 +++- .../compiler/xla/client/executable_build_options.h | 9 +++++---- tensorflow/compiler/xla/service/local_service.cc | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tensorflow/compiler/xla/client/executable_build_options.cc b/tensorflow/compiler/xla/client/executable_build_options.cc index 4ff4da6215..6e3c5cb484 100644 --- a/tensorflow/compiler/xla/client/executable_build_options.cc +++ b/tensorflow/compiler/xla/client/executable_build_options.cc @@ -103,6 +103,8 @@ ExecutableBuildOptions& ExecutableBuildOptions::set_hlo_profile(bool enabled) { return *this; } -bool ExecutableBuildOptions::hlo_profile() const { return hlo_profile_; } +tensorflow::gtl::optional ExecutableBuildOptions::hlo_profile() const { + return hlo_profile_; +} } // namespace xla diff --git a/tensorflow/compiler/xla/client/executable_build_options.h b/tensorflow/compiler/xla/client/executable_build_options.h index 85b2cd96cb..11f1098360 100644 --- a/tensorflow/compiler/xla/client/executable_build_options.h +++ b/tensorflow/compiler/xla/client/executable_build_options.h @@ -70,17 +70,18 @@ class ExecutableBuildOptions { tensorflow::StringPiece dirpath); const tensorflow::gtl::optional& dump_per_pass_hlo_proto_to() const; - // If set, specifies that we should record an HLO profile during execution and - // log it after execution (as in DebugOptions). + // If true, specifies that we should record an HLO profile during execution + // and log it after execution (as in DebugOptions). If nullopt the default is + // used. ExecutableBuildOptions& set_hlo_profile(bool enabled); - bool hlo_profile() const; + tensorflow::gtl::optional hlo_profile() const; // Returns a string representation of the build options, suitable for // debugging. string ToString() const; private: - bool hlo_profile_ = false; + tensorflow::gtl::optional hlo_profile_; int device_ordinal_ = -1; Shape result_layout_; bool result_layout_set_ = false; diff --git a/tensorflow/compiler/xla/service/local_service.cc b/tensorflow/compiler/xla/service/local_service.cc index 7fd1ccd1a8..5690a89909 100644 --- a/tensorflow/compiler/xla/service/local_service.cc +++ b/tensorflow/compiler/xla/service/local_service.cc @@ -119,8 +119,10 @@ StatusOr> LocalService::CompileExecutable( } ExecutionOptions execution_options = CreateDefaultExecutionOptions(); - execution_options.mutable_debug_options()->set_xla_hlo_profile( - build_options.hlo_profile()); + if (build_options.hlo_profile().has_value()) { + execution_options.mutable_debug_options()->set_xla_hlo_profile( + *build_options.hlo_profile()); + } if (build_options.generate_hlo_graph().has_value()) { execution_options.mutable_debug_options()->set_xla_generate_hlo_graph( build_options.generate_hlo_graph().value()); -- GitLab From 18c6e42b95dab659aa755242096cda9195db4927 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 09:43:32 -0700 Subject: [PATCH 073/906] Simplifying "is" and "is not" dispatch PiperOrigin-RevId: 190078959 --- tensorflow/contrib/py2tf/utils/multiple_dispatch.py | 13 ++++--------- .../contrib/py2tf/utils/multiple_dispatch_test.py | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/tensorflow/contrib/py2tf/utils/multiple_dispatch.py b/tensorflow/contrib/py2tf/utils/multiple_dispatch.py index da7a942703..427a936c35 100644 --- a/tensorflow/contrib/py2tf/utils/multiple_dispatch.py +++ b/tensorflow/contrib/py2tf/utils/multiple_dispatch.py @@ -22,21 +22,16 @@ import six from tensorflow.contrib.py2tf.utils.type_check import is_tensor from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import math_ops def dynamic_is(left, right): - if is_tensor(left, right): - return math_ops.equal(left.name, right.name) - else: - return left is right + # TODO(alexbw) if we're sure we should leave 'is' in place, + # then change the semantics in converters/logical_expressions.py + return left is right def dynamic_is_not(left, right): - if is_tensor(left, right): - return math_ops.not_equal(left.name, right.name) - else: - return left is not right + return left is not right def run_cond(condition, true_fn, false_fn): diff --git a/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py b/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py index 8d89b6898a..75e8fdd5ed 100644 --- a/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py +++ b/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py @@ -50,10 +50,10 @@ class MultipleDispatchTest(test.TestCase): should_be_false1 = multiple_dispatch.dynamic_is_not(a, also_a) should_be_true2 = multiple_dispatch.dynamic_is_not(a, not_actually_a) should_be_false2 = multiple_dispatch.dynamic_is(a, not_actually_a) - self.assertTrue(should_be_true1.eval()) - self.assertTrue(should_be_true2.eval()) - self.assertFalse(should_be_false1.eval()) - self.assertFalse(should_be_false2.eval()) + self.assertTrue(should_be_true1) + self.assertTrue(should_be_true2) + self.assertFalse(should_be_false1) + self.assertFalse(should_be_false2) def test_run_cond_python(self): true_fn = lambda: 2.0 -- GitLab From 9816741186cdf327e1ee9fb048f1573356ac1064 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 10:10:18 -0700 Subject: [PATCH 074/906] Template system improvements: allow replacing keyword args. Allow using function calls and dicts in name replacements. PiperOrigin-RevId: 190083700 --- tensorflow/contrib/py2tf/pyct/templates.py | 59 ++++++++++++++++++- .../contrib/py2tf/pyct/templates_test.py | 45 ++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/py2tf/pyct/templates.py b/tensorflow/contrib/py2tf/pyct/templates.py index cdd71dc56d..590be68234 100644 --- a/tensorflow/contrib/py2tf/pyct/templates.py +++ b/tensorflow/contrib/py2tf/pyct/templates.py @@ -44,8 +44,6 @@ class ReplaceTransformer(gast.NodeTransformer): self.replacements = replacements self.in_replacements = False - # TODO(mdan): Make a more detailed pass and clean up if needed. - def visit_Expr(self, node): if (isinstance(node.value, gast.Name) and node.value.id in self.replacements): @@ -53,17 +51,57 @@ class ReplaceTransformer(gast.NodeTransformer): self.generic_visit(node) return node + def visit_keyword(self, node): + if node.arg in self.replacements: + repl = self.replacements[node.arg] + if isinstance(repl, gast.keyword): + return repl + elif (isinstance(repl, (list, tuple)) and repl and + all(isinstance(r, gast.keyword) for r in repl)): + return repl + # TODO(mdan): We may allow replacing with a string as well. + # For example, if one wanted to replace foo with bar in foo=baz, then + # we could allow changing just node arg, so that we end up with bar=baz. + raise ValueError( + 'a keyword argument may only be replaced by another keyword or a ' + 'non-empty list of keywords. Found: %s' % repl) + return self.generic_visit(node) + def visit_FunctionDef(self, node): node = self.generic_visit(node) if node.name in self.replacements: repl = self.replacements[node.name] if not isinstance(repl, (gast.Name, ast.Name)): raise ValueError( - 'A function name can only be replaced by a Name node. Found: %s' % + 'a function name can only be replaced by a Name node. Found: %s' % repl) node.name = repl.id return node + def _check_has_context(self, node): + if not node.ctx: + raise ValueError('node %s is missing ctx value' % node) + + def _check_inner_children_have_context(self, node): + if isinstance(node, gast.Attribute): + self._check_inner_children_have_context(node.value) + self._check_has_context(node) + elif isinstance(node, gast.Tuple): + for e in node.elts: + self._check_inner_children_have_context(e) + self._check_has_context(node) + elif isinstance(node, gast.Dict): + for e in node.keys: + self._check_inner_children_have_context(e) + for e in node.values: + self._check_inner_children_have_context(e) + elif isinstance(node, gast.Name): + self._check_has_context(node) + elif isinstance(node, (gast.Str, gast.Num)): + pass + else: + raise ValueError('unexpected node type "%s"' % node) + def _set_inner_child_context(self, node, ctx): if isinstance(node, gast.Attribute): self._set_inner_child_context(node.value, ctx) @@ -74,6 +112,21 @@ class ReplaceTransformer(gast.NodeTransformer): node.ctx = ctx elif isinstance(node, gast.Name): node.ctx = ctx + elif isinstance(node, gast.Call): + self._set_inner_child_context(node.func, ctx) + # We may be able to override these to Load(), but for now it's simpler + # to just assert that they're set. + for a in node.args: + self._check_inner_children_have_context(a) + for k in node.keywords: + self._check_inner_children_have_context(k.value) + elif isinstance(node, gast.Dict): + # We may be able to override these to Load(), but for now it's simpler + # to just assert that they're set. + for e in node.keys: + self._check_inner_children_have_context(e) + for e in node.values: + self._check_inner_children_have_context(e) elif isinstance(node, (gast.Str, gast.Num)): pass else: diff --git a/tensorflow/contrib/py2tf/pyct/templates_test.py b/tensorflow/contrib/py2tf/pyct/templates_test.py index d7835b80a7..af939caf32 100644 --- a/tensorflow/contrib/py2tf/pyct/templates_test.py +++ b/tensorflow/contrib/py2tf/pyct/templates_test.py @@ -23,6 +23,7 @@ import imp import gast from tensorflow.contrib.py2tf.pyct import compiler +from tensorflow.contrib.py2tf.pyct import parser from tensorflow.contrib.py2tf.pyct import templates from tensorflow.python.platform import test @@ -96,6 +97,50 @@ class TemplatesTest(test.TestCase): with self.assertRaises(ValueError): templates.replace(template, foo=1) + def test_replace_call_keyword(self): + template = """ + def test_fn(): + def f(a, d, f): + return a + d + f + return f(1, kws=None) + """ + + source = parser.parse_expression('f(d=3, f=5)') + node = templates.replace(template, kws=source.keywords)[0] + result, _ = compiler.ast_to_object(node) + self.assertEquals(9, result.test_fn()) + + with self.assertRaises(ValueError): + templates.replace(template, kws=[]) + templates.replace(template, kws=1) + + def test_replace_name_with_call(self): + template = """ + def test_fn(): + b = 5 + def g(a): + return 3 * a + def f(): + return g + return foo + """ + + source = parser.parse_expression('f()(b)') + node = templates.replace(template, foo=source)[0] + result, _ = compiler.ast_to_object(node) + self.assertEquals(15, result.test_fn()) + + def test_replace_name_with_dict(self): + template = """ + def test_fn(): + return foo['bar'] + """ + + source = parser.parse_expression('{\'bar\': 3}') + node = templates.replace(template, foo=source)[0] + result, _ = compiler.ast_to_object(node) + self.assertEquals(3, result.test_fn()) + def replace_as_expression(self): template = """ foo(a) -- GitLab From aeb9f62e237ae1274482acca2fa09db34aef42d4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 10:17:18 -0700 Subject: [PATCH 075/906] Omit tensorflow/python/estimator:replicate_model_fn_test from asan builds. It gets flaky timeouts. PiperOrigin-RevId: 190084932 --- tensorflow/python/estimator/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/estimator/BUILD b/tensorflow/python/estimator/BUILD index 04fcbb0e87..b25f9d2153 100644 --- a/tensorflow/python/estimator/BUILD +++ b/tensorflow/python/estimator/BUILD @@ -929,5 +929,6 @@ cuda_py_test( ], tags = [ "multi_gpu", + "noasan", # flaky time outs ], ) -- GitLab From 010e3e401cef883aa0fff334d3f5e56a88e3f5e4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 10:21:39 -0700 Subject: [PATCH 076/906] Mark tensor as const in a function that does not mutate a tensor. PiperOrigin-RevId: 190085757 --- tensorflow/contrib/lite/kernels/kernel_util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/kernel_util.h b/tensorflow/contrib/lite/kernels/kernel_util.h index 21da1daff7..2f407b5da3 100644 --- a/tensorflow/contrib/lite/kernels/kernel_util.h +++ b/tensorflow/contrib/lite/kernels/kernel_util.h @@ -53,13 +53,13 @@ inline TfLiteTensor* GetOptionalInputTensor(TfLiteContext* context, } // Determines whether tensor is constant. -inline bool IsConstantTensor(TfLiteTensor* tensor) { +inline bool IsConstantTensor(const TfLiteTensor* tensor) { return tensor->allocation_type == kTfLiteMmapRo; } // Determines whether tensor is dynamic. Note that a tensor can be non-const and // not dynamic. This function specifically checks for a dynamic tensor. -inline bool IsDynamicTensor(TfLiteTensor* tensor) { +inline bool IsDynamicTensor(const TfLiteTensor* tensor) { return tensor->allocation_type == kTfLiteDynamic; } -- GitLab From ed0c4037ec47e3a7d1e5d23514951e5256b8a30f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 10:21:59 -0700 Subject: [PATCH 077/906] Small cleanup StrCat related number formatting - Resolve inconsistency in return values (pointer to start vs end of buffer) - Instead, return the number of chars written as this turns out to be most useful to callers - Removes the need for redundant strlen calls. PiperOrigin-RevId: 190085812 --- tensorflow/core/lib/strings/numbers.cc | 49 +++++++++++++++----------- tensorflow/core/lib/strings/numbers.h | 15 ++++---- tensorflow/core/lib/strings/strcat.h | 16 ++++----- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/tensorflow/core/lib/strings/numbers.cc b/tensorflow/core/lib/strings/numbers.cc index f5822fad8e..516decc3c0 100644 --- a/tensorflow/core/lib/strings/numbers.cc +++ b/tensorflow/core/lib/strings/numbers.cc @@ -106,19 +106,22 @@ T locale_independent_strtonum(const char* str, const char** endptr) { namespace strings { -char* FastInt32ToBufferLeft(int32 i, char* buffer) { +size_t FastInt32ToBufferLeft(int32 i, char* buffer) { uint32 u = i; + size_t length = 0; if (i < 0) { *buffer++ = '-'; + ++length; // We need to do the negation in modular (i.e., "unsigned") // arithmetic; MSVC++ apparently warns for plain "-u", so // we write the equivalent expression "0 - u" instead. u = 0 - u; } - return FastUInt32ToBufferLeft(u, buffer); + length += FastUInt32ToBufferLeft(u, buffer); + return length; } -char* FastUInt32ToBufferLeft(uint32 i, char* buffer) { +size_t FastUInt32ToBufferLeft(uint32 i, char* buffer) { char* start = buffer; do { *buffer++ = ((i % 10) + '0'); @@ -126,19 +129,22 @@ char* FastUInt32ToBufferLeft(uint32 i, char* buffer) { } while (i > 0); *buffer = 0; std::reverse(start, buffer); - return buffer; + return buffer - start; } -char* FastInt64ToBufferLeft(int64 i, char* buffer) { +size_t FastInt64ToBufferLeft(int64 i, char* buffer) { uint64 u = i; + size_t length = 0; if (i < 0) { *buffer++ = '-'; + ++length; u = 0 - u; } - return FastUInt64ToBufferLeft(u, buffer); + length += FastUInt64ToBufferLeft(u, buffer); + return length; } -char* FastUInt64ToBufferLeft(uint64 i, char* buffer) { +size_t FastUInt64ToBufferLeft(uint64 i, char* buffer) { char* start = buffer; do { *buffer++ = ((i % 10) + '0'); @@ -146,19 +152,18 @@ char* FastUInt64ToBufferLeft(uint64 i, char* buffer) { } while (i > 0); *buffer = 0; std::reverse(start, buffer); - return buffer; + return buffer - start; } static const double kDoublePrecisionCheckMax = DBL_MAX / 1.000000000000001; -char* DoubleToBuffer(double value, char* buffer) { +size_t DoubleToBuffer(double value, char* buffer) { // DBL_DIG is 15 for IEEE-754 doubles, which are used on almost all // platforms these days. Just in case some system exists where DBL_DIG // is significantly larger -- and risks overflowing our buffer -- we have // this assert. static_assert(DBL_DIG < 20, "DBL_DIG is too big"); - bool full_precision_needed = true; if (std::abs(value) <= kDoublePrecisionCheckMax) { int snprintf_result = snprintf(buffer, kFastToBufferSize, "%.*g", DBL_DIG, value); @@ -167,18 +172,20 @@ char* DoubleToBuffer(double value, char* buffer) { // larger than the precision we asked for. DCHECK(snprintf_result > 0 && snprintf_result < kFastToBufferSize); - full_precision_needed = - locale_independent_strtonum(buffer, nullptr) != value; + if (locale_independent_strtonum(buffer, nullptr) == value) { + // Round-tripping the string to double works; we're done. + return snprintf_result; + } + // else: full precision formatting needed. Fall through. } - if (full_precision_needed) { - int snprintf_result = - snprintf(buffer, kFastToBufferSize, "%.*g", DBL_DIG + 2, value); + int snprintf_result = + snprintf(buffer, kFastToBufferSize, "%.*g", DBL_DIG + 2, value); - // Should never overflow; see above. - DCHECK(snprintf_result > 0 && snprintf_result < kFastToBufferSize); - } - return buffer; + // Should never overflow; see above. + DCHECK(snprintf_result > 0 && snprintf_result < kFastToBufferSize); + + return snprintf_result; } namespace { @@ -325,7 +332,7 @@ bool safe_strtod(const char* str, double* value) { return *str != '\0' && *endptr == '\0'; } -char* FloatToBuffer(float value, char* buffer) { +size_t FloatToBuffer(float value, char* buffer) { // FLT_DIG is 6 for IEEE-754 floats, which are used on almost all // platforms these days. Just in case some system exists where FLT_DIG // is significantly larger -- and risks overflowing our buffer -- we have @@ -347,7 +354,7 @@ char* FloatToBuffer(float value, char* buffer) { // Should never overflow; see above. DCHECK(snprintf_result > 0 && snprintf_result < kFastToBufferSize); } - return buffer; + return snprintf_result; } string FpToString(Fprint fp) { diff --git a/tensorflow/core/lib/strings/numbers.h b/tensorflow/core/lib/strings/numbers.h index 3c45b90274..6b7703be37 100644 --- a/tensorflow/core/lib/strings/numbers.h +++ b/tensorflow/core/lib/strings/numbers.h @@ -60,19 +60,18 @@ static const int kFastToBufferSize = 32; // the output. The buffer should typically be at least kFastToBufferSize // bytes. // -// Returns a pointer to the end of the string (i.e. the null character -// terminating the string). +// Returns the number of characters written. // ---------------------------------------------------------------------- -char* FastInt32ToBufferLeft(int32 i, char* buffer); // at least 12 bytes -char* FastUInt32ToBufferLeft(uint32 i, char* buffer); // at least 12 bytes -char* FastInt64ToBufferLeft(int64 i, char* buffer); // at least 22 bytes -char* FastUInt64ToBufferLeft(uint64 i, char* buffer); // at least 22 bytes +size_t FastInt32ToBufferLeft(int32 i, char* buffer); // at least 12 bytes +size_t FastUInt32ToBufferLeft(uint32 i, char* buffer); // at least 12 bytes +size_t FastInt64ToBufferLeft(int64 i, char* buffer); // at least 22 bytes +size_t FastUInt64ToBufferLeft(uint64 i, char* buffer); // at least 22 bytes // Required buffer size for DoubleToBuffer is kFastToBufferSize. // Required buffer size for FloatToBuffer is kFastToBufferSize. -char* DoubleToBuffer(double i, char* buffer); -char* FloatToBuffer(float i, char* buffer); +size_t DoubleToBuffer(double value, char* buffer); +size_t FloatToBuffer(float value, char* buffer); // Convert a 64-bit fingerprint value to an ASCII representation. string FpToString(Fprint fp); diff --git a/tensorflow/core/lib/strings/strcat.h b/tensorflow/core/lib/strings/strcat.h index b3ec14e448..fb2cd5bc7e 100644 --- a/tensorflow/core/lib/strings/strcat.h +++ b/tensorflow/core/lib/strings/strcat.h @@ -101,22 +101,22 @@ class AlphaNum { // A bool ctor would also convert incoming pointers (bletch). AlphaNum(int i32) // NOLINT(runtime/explicit) - : piece_(digits_, FastInt32ToBufferLeft(i32, digits_) - &digits_[0]) {} + : piece_(digits_, FastInt32ToBufferLeft(i32, digits_)) {} AlphaNum(unsigned int u32) // NOLINT(runtime/explicit) - : piece_(digits_, FastUInt32ToBufferLeft(u32, digits_) - &digits_[0]) {} + : piece_(digits_, FastUInt32ToBufferLeft(u32, digits_)) {} AlphaNum(long x) // NOLINT(runtime/explicit) - : piece_(digits_, FastInt64ToBufferLeft(x, digits_) - &digits_[0]) {} + : piece_(digits_, FastInt64ToBufferLeft(x, digits_)) {} AlphaNum(unsigned long x) // NOLINT(runtime/explicit) - : piece_(digits_, FastUInt64ToBufferLeft(x, digits_) - &digits_[0]) {} + : piece_(digits_, FastUInt64ToBufferLeft(x, digits_)) {} AlphaNum(long long int i64) // NOLINT(runtime/explicit) - : piece_(digits_, FastInt64ToBufferLeft(i64, digits_) - &digits_[0]) {} + : piece_(digits_, FastInt64ToBufferLeft(i64, digits_)) {} AlphaNum(unsigned long long int u64) // NOLINT(runtime/explicit) - : piece_(digits_, FastUInt64ToBufferLeft(u64, digits_) - &digits_[0]) {} + : piece_(digits_, FastUInt64ToBufferLeft(u64, digits_)) {} AlphaNum(float f) // NOLINT(runtime/explicit) - : piece_(digits_, strlen(FloatToBuffer(f, digits_))) {} + : piece_(digits_, FloatToBuffer(f, digits_)) {} AlphaNum(double f) // NOLINT(runtime/explicit) - : piece_(digits_, strlen(DoubleToBuffer(f, digits_))) {} + : piece_(digits_, DoubleToBuffer(f, digits_)) {} AlphaNum(Hex hex); // NOLINT(runtime/explicit) -- GitLab From 6fa811a94f3da0c49d69db9b15ea424f84a6431f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 10:28:56 -0700 Subject: [PATCH 078/906] Automated g4 rollback of changelist 189819449 PiperOrigin-RevId: 190087074 --- tensorflow/contrib/distributions/BUILD | 6 ++- .../kernel_tests/statistical_testing_test.py | 40 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tensorflow/contrib/distributions/BUILD b/tensorflow/contrib/distributions/BUILD index 4ddec73ec8..e9c827a618 100644 --- a/tensorflow/contrib/distributions/BUILD +++ b/tensorflow/contrib/distributions/BUILD @@ -486,7 +486,11 @@ cuda_py_test( "//third_party/py/numpy", "//tensorflow/python:client_testlib", ], - tags = ["noasan"], # Was found to time out in asan + tags = [ + "manual", + "noasan", + "noguitar", + ], ) cuda_py_test( diff --git a/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py b/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py index fc071c273d..3548ac1807 100644 --- a/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py +++ b/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py @@ -31,34 +31,30 @@ class StatisticalTestingTest(test.TestCase): def test_dkwm_design_mean_one_sample_soundness(self): numbers = [1e-5, 1e-2, 1.1e-1, 0.9, 1., 1.02, 2., 10., 1e2, 1e5, 1e10] rates = [1e-6, 1e-3, 1e-2, 1.1e-1, 0.2, 0.5, 0.7, 1.] - def check_soundness(ff, fp): - sufficient_n = st.min_num_samples_for_dkwm_mean_test( - numbers, 0., 1., false_fail_rate=ff, false_pass_rate=fp) - detectable_d = st.min_discrepancy_of_true_means_detectable_by_dkwm( - sufficient_n, 0., 1., false_fail_rate=ff, false_pass_rate=fp) - return check_ops.assert_less_equal(detectable_d, numbers) with self.test_session() as sess: - sess.run([check_soundness(ff, fp) - for ff in rates - for fp in rates]) + for ff in rates: + for fp in rates: + sufficient_n = st.min_num_samples_for_dkwm_mean_test( + numbers, 0., 1., false_fail_rate=ff, false_pass_rate=fp) + detectable_d = st.min_discrepancy_of_true_means_detectable_by_dkwm( + sufficient_n, 0., 1., false_fail_rate=ff, false_pass_rate=fp) + sess.run(check_ops.assert_less_equal(detectable_d, numbers)) def test_dkwm_design_mean_two_sample_soundness(self): numbers = [1e-5, 1e-2, 1.1e-1, 0.9, 1., 1.02, 2., 10., 1e2, 1e5, 1e10] rates = [1e-6, 1e-3, 1e-2, 1.1e-1, 0.2, 0.5, 0.7, 1.] - def check_soundness(ff, fp): - (sufficient_n1, - sufficient_n2) = st.min_num_samples_for_dkwm_mean_two_sample_test( - numbers, 0., 1., 0., 1., - false_fail_rate=ff, false_pass_rate=fp) - d_fn = st.min_discrepancy_of_true_means_detectable_by_dkwm_two_sample - detectable_d = d_fn( - sufficient_n1, 0., 1., sufficient_n2, 0., 1., - false_fail_rate=ff, false_pass_rate=fp) - return check_ops.assert_less_equal(detectable_d, numbers) with self.test_session() as sess: - sess.run([check_soundness(ff, fp) - for ff in rates - for fp in rates]) + for ff in rates: + for fp in rates: + (sufficient_n1, + sufficient_n2) = st.min_num_samples_for_dkwm_mean_two_sample_test( + numbers, 0., 1., 0., 1., + false_fail_rate=ff, false_pass_rate=fp) + d_fn = st.min_discrepancy_of_true_means_detectable_by_dkwm_two_sample + detectable_d = d_fn( + sufficient_n1, 0., 1., sufficient_n2, 0., 1., + false_fail_rate=ff, false_pass_rate=fp) + sess.run(check_ops.assert_less_equal(detectable_d, numbers)) def test_true_mean_confidence_interval_by_dkwm_one_sample(self): rng = np.random.RandomState(seed=0) -- GitLab From 1a6752dddf387d280a6a13c2dc7e2bebf69dab2f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 11:12:10 -0700 Subject: [PATCH 079/906] Adds remaining validations in sequence_numeric_column. PiperOrigin-RevId: 190094883 --- .../feature_column/sequence_feature_column.py | 32 ++++++++++++++++++- .../sequence_feature_column_test.py | 26 +++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column.py b/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column.py index e60116966f..555beddeaa 100644 --- a/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column.py +++ b/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column.py @@ -166,6 +166,10 @@ def sequence_categorical_column_with_identity( Returns: A `_SequenceCategoricalColumn`. + + Raises: + ValueError: if `num_buckets` is less than one. + ValueError: if `default_value` is not in range `[0, num_buckets)`. """ return fc._SequenceCategoricalColumn( fc.categorical_column_with_identity( @@ -205,6 +209,10 @@ def sequence_categorical_column_with_hash_bucket( Returns: A `_SequenceCategoricalColumn`. + + Raises: + ValueError: `hash_bucket_size` is not greater than 1. + ValueError: `dtype` is neither string nor integer. """ return fc._SequenceCategoricalColumn( fc.categorical_column_with_hash_bucket( @@ -257,6 +265,13 @@ def sequence_categorical_column_with_vocabulary_file( Returns: A `_SequenceCategoricalColumn`. + + Raises: + ValueError: `vocabulary_file` is missing or cannot be opened. + ValueError: `vocabulary_size` is missing or < 1. + ValueError: `num_oov_buckets` is a negative integer. + ValueError: `num_oov_buckets` and `default_value` are both specified. + ValueError: `dtype` is neither string nor integer. """ return fc._SequenceCategoricalColumn( fc.categorical_column_with_vocabulary_file( @@ -311,6 +326,12 @@ def sequence_categorical_column_with_vocabulary_list( Returns: A `_SequenceCategoricalColumn`. + + Raises: + ValueError: if `vocabulary_list` is empty, or contains duplicate keys. + ValueError: `num_oov_buckets` is a negative integer. + ValueError: `num_oov_buckets` and `default_value` are both specified. + ValueError: if `dtype` is not integer or string. """ return fc._SequenceCategoricalColumn( fc.categorical_column_with_vocabulary_list( @@ -352,8 +373,17 @@ def sequence_numeric_column( Returns: A `_SequenceNumericColumn`. + + Raises: + TypeError: if any dimension in shape is not an int. + ValueError: if any dimension in shape is not a positive integer. + ValueError: if `dtype` is not convertible to `tf.float32`. """ - # TODO(b/73160931): Add validations. + shape = fc._check_shape(shape=shape, key=key) + if not (dtype.is_integer or dtype.is_floating): + raise ValueError('dtype must be convertible to float. ' + 'dtype: {}, key: {}'.format(dtype, key)) + return _SequenceNumericColumn( key, shape=shape, diff --git a/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column_test.py b/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column_test.py index b64f086376..88f5d53516 100644 --- a/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column_test.py +++ b/tensorflow/contrib/feature_column/python/feature_column/sequence_feature_column_test.py @@ -662,6 +662,32 @@ class SequenceIndicatorColumnTest(test.TestCase): class SequenceNumericColumnTest(test.TestCase): + def test_defaults(self): + a = sfc.sequence_numeric_column('aaa') + self.assertEqual('aaa', a.key) + self.assertEqual('aaa', a.name) + self.assertEqual('aaa', a._var_scope_name) + self.assertEqual((1,), a.shape) + self.assertEqual(0., a.default_value) + self.assertEqual(dtypes.float32, a.dtype) + + def test_shape_saved_as_tuple(self): + a = sfc.sequence_numeric_column('aaa', shape=[1, 2]) + self.assertEqual((1, 2), a.shape) + + def test_shape_must_be_positive_integer(self): + with self.assertRaisesRegexp(TypeError, 'shape dimensions must be integer'): + sfc.sequence_numeric_column('aaa', shape=[1.0]) + + with self.assertRaisesRegexp( + ValueError, 'shape dimensions must be greater than 0'): + sfc.sequence_numeric_column('aaa', shape=[0]) + + def test_dtype_is_convertible_to_float(self): + with self.assertRaisesRegexp( + ValueError, 'dtype must be convertible to float'): + sfc.sequence_numeric_column('aaa', dtype=dtypes.string) + def test_get_sequence_dense_tensor(self): sparse_input = sparse_tensor.SparseTensorValue( # example 0, values [[0.], [1]] -- GitLab From 8991aeea540da49344ceac6e8f5e092778f410a9 Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Thu, 22 Mar 2018 11:21:52 -0700 Subject: [PATCH 080/906] Eager/g3doc: Gradients with respect to constants are None and not 0. Same behavior as tf.gradients() for graphs. Some discussion of this choice in #783 PiperOrigin-RevId: 190096919 --- tensorflow/contrib/eager/python/g3doc/guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/eager/python/g3doc/guide.md b/tensorflow/contrib/eager/python/g3doc/guide.md index df084e9053..11064981c6 100644 --- a/tensorflow/contrib/eager/python/g3doc/guide.md +++ b/tensorflow/contrib/eager/python/g3doc/guide.md @@ -273,9 +273,9 @@ assert 6 == df(3.)[0].numpy() d2f = tfe.gradients_function(lambda x: df(x)[0]) assert 2 == d2f(3.)[0].numpy() -# Third order derivative. +# Third order derivative: Will be None d3f = tfe.gradients_function(lambda x : d2f(x)[0]) -assert 0 == d3f(3.)[0].numpy() +assert None == d3f(3.)[0] ``` These functions can be used to train models. For example, consider the following -- GitLab From cfdd61585769188789280e768fc43fdbba799619 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Thu, 22 Mar 2018 11:23:26 -0700 Subject: [PATCH 081/906] Run the grappler optimizer tests both on GPU and CPU PiperOrigin-RevId: 190097168 --- tensorflow/core/grappler/optimizers/BUILD | 27 +++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index ac29edd213..cfb698969c 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -1,7 +1,7 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") -load("//tensorflow:tensorflow.bzl", "tf_cc_test_gpu") +load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_test") load("//tensorflow:tensorflow.bzl", "tf_kernel_library") load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda") @@ -44,7 +44,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "static_schedule_test", srcs = ["static_schedule_test.cc"], deps = [ @@ -79,7 +79,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "auto_parallel_test", srcs = ["auto_parallel_test.cc"], deps = [ @@ -157,7 +157,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "function_optimizer_test", srcs = ["function_optimizer_test.cc"], deps = [ @@ -223,7 +223,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "graph_optimizer_stage_test", size = "small", srcs = ["graph_optimizer_stage_test.cc"], @@ -274,7 +274,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "arithmetic_optimizer_test", size = "small", srcs = ["arithmetic_optimizer_test.cc"], @@ -315,7 +315,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "dependency_optimizer_test", size = "small", srcs = ["dependency_optimizer_test.cc"], @@ -351,7 +351,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "model_pruner_test", srcs = ["model_pruner_test.cc"], deps = [ @@ -422,10 +422,9 @@ cc_library( ]), ) -tf_cc_test_gpu( +tf_cuda_cc_test( name = "memory_optimizer_test", srcs = ["memory_optimizer_test.cc"], - tags = ["no_cuda_on_cpu_tap"], deps = [ ":memory_optimizer", "//tensorflow/cc:cc_ops", @@ -464,7 +463,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "layout_optimizer_test", srcs = ["layout_optimizer_test.cc"], deps = [ @@ -513,7 +512,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "meta_optimizer_test", srcs = ["meta_optimizer_test.cc"], deps = [ @@ -542,7 +541,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "custom_graph_optimizer_registry_test", size = "small", srcs = ["custom_graph_optimizer_registry_test.cc"], @@ -577,7 +576,7 @@ cc_library( ], ) -tf_cc_test( +tf_cuda_cc_test( name = "loop_optimizer_test", srcs = ["loop_optimizer_test.cc"], deps = [ -- GitLab From 7c4cdb8bae0e8760ebe4793d49ea5aee68768655 Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 22 Mar 2018 11:25:49 -0700 Subject: [PATCH 082/906] Supports PReLU in TFLite & Toco. PiperOrigin-RevId: 190097557 --- tensorflow/contrib/lite/builtin_ops.h | 1 + .../contrib/lite/kernels/activations.cc | 64 ++++++++++ .../contrib/lite/kernels/activations_test.cc | 43 +++++++ tensorflow/contrib/lite/kernels/register.cc | 2 + tensorflow/contrib/lite/model.cc | 1 + tensorflow/contrib/lite/nnapi_delegate.cc | 1 + tensorflow/contrib/lite/schema/schema.fbs | 1 + .../contrib/lite/schema/schema_generated.h | 9 +- tensorflow/contrib/lite/testing/BUILD | 1 + .../contrib/lite/testing/generate_examples.py | 49 ++++++++ .../testing/generated_examples_zip_test.cc | 4 + tensorflow/contrib/lite/toco/BUILD | 1 + .../graph_transformations.h | 1 + .../graph_transformations/identify_prelu.cc | 119 ++++++++++++++++++ .../propagate_fixed_sizes.cc | 1 + tensorflow/contrib/lite/toco/model.h | 13 ++ .../contrib/lite/toco/tflite/operator.cc | 2 + tensorflow/contrib/lite/toco/toco_tooling.cc | 1 + tensorflow/contrib/lite/toco/tooling_util.cc | 1 + 19 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 tensorflow/contrib/lite/toco/graph_transformations/identify_prelu.cc diff --git a/tensorflow/contrib/lite/builtin_ops.h b/tensorflow/contrib/lite/builtin_ops.h index e4652a3e70..d7993e60cc 100644 --- a/tensorflow/contrib/lite/builtin_ops.h +++ b/tensorflow/contrib/lite/builtin_ops.h @@ -78,6 +78,7 @@ typedef enum { kTfLiteBuiltinDelegate = 51, kTfLiteBuiltinBidirectionalSequenceLstm = 52, kTfLiteBuiltinCast = 53, + kTfLiteBuiltinPrelu = 54, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/contrib/lite/kernels/activations.cc b/tensorflow/contrib/lite/kernels/activations.cc index 093761c43c..39a54c9396 100644 --- a/tensorflow/contrib/lite/kernels/activations.cc +++ b/tensorflow/contrib/lite/kernels/activations.cc @@ -150,6 +150,34 @@ TfLiteStatus SoftmaxPrepare(TfLiteContext* context, TfLiteNode* node) { TfLiteIntArrayCopy(input->dims)); } +TfLiteStatus PreluPrepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + TfLiteTensor* input = GetInput(context, node, 0); + TfLiteTensor* output = GetOutput(context, node, 0); + TfLiteTensor* alpha = GetInput(context, node, 1); + + output->type = input->type; + + // Currently only Float32 is supported + // TODO(ycling): Support other data types. + TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); + TF_LITE_ENSURE_EQ(context, alpha->type, kTfLiteFloat32); + + // Currently, only support 4D `input` and 3D `alpha` with shape + // (1, 1, channels). + // TODO(impjdi): Support other cases where `alpha` is broadcastable + // to `input`. + TF_LITE_ENSURE_EQ(context, input->dims->size, 4); + TF_LITE_ENSURE_EQ(context, alpha->dims->size, 3); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[0], 1); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[1], 1); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[2], input->dims->data[3]); + + return context->ResizeTensor(context, output, + TfLiteIntArrayCopy(input->dims)); +} + TfLiteStatus ReluEval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* input = GetInput(context, node, 0); TfLiteTensor* output = GetOutput(context, node, 0); @@ -388,6 +416,35 @@ TfLiteStatus LogSoftmaxEval(TfLiteContext* context, TfLiteNode* node) { } } +TfLiteStatus PreluEval(TfLiteContext* context, TfLiteNode* node) { + TfLiteTensor* input = GetInput(context, node, 0); + TfLiteTensor* alpha = GetInput(context, node, 1); + TfLiteTensor* output = GetOutput(context, node, 0); + + if (input->type != kTfLiteFloat32) { + context->ReportError(context, "Only float32 supported currently."); + return kTfLiteError; + } + TF_LITE_ENSURE_EQ(context, input->dims->size, 4); + const int batches = input->dims->data[0]; + const int height = input->dims->data[1]; + const int width = input->dims->data[2]; + const int channels = input->dims->data[3]; + + TF_LITE_ENSURE_EQ(context, alpha->dims->size, 3); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[0], 1); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[1], 1); + TF_LITE_ENSURE_EQ(context, alpha->dims->data[2], channels); + + const int n = batches * height * width * channels; + for (int i = 0; i < n; ++i) { + const float x = input->data.f[i]; + output->data.f[i] = x >= 0.0f ? x : alpha->data.f[i % channels] * x; + } + + return kTfLiteOk; +} + } // namespace activations TfLiteRegistration* Register_RELU() { @@ -439,6 +496,13 @@ TfLiteRegistration* Register_LOG_SOFTMAX() { return &r; } +TfLiteRegistration* Register_PRELU() { + static TfLiteRegistration r = {/*init=*/nullptr, /*free=*/nullptr, + activations::PreluPrepare, + activations::PreluEval}; + return &r; +} + } // namespace builtin } // namespace ops } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/activations_test.cc b/tensorflow/contrib/lite/kernels/activations_test.cc index b9a96e3f79..50a84edd47 100644 --- a/tensorflow/contrib/lite/kernels/activations_test.cc +++ b/tensorflow/contrib/lite/kernels/activations_test.cc @@ -383,6 +383,49 @@ TEST(FloatActivationsOpTest, LogSoftmax) { }))); } +class PReluOpModel : public SingleOpModel { + public: + PReluOpModel(const TensorData& input, const TensorData& alpha) { + input_ = AddInput(input); + alpha_ = AddInput(alpha); + output_ = AddOutput(input); + SetBuiltinOp(BuiltinOperator_PRELU, BuiltinOptions_NONE, 0); + BuildInterpreter({GetShape(input_), GetShape(alpha_)}); + } + void SetInput(std::initializer_list data) { + PopulateTensor(input_, data); + } + void SetAlpha(std::initializer_list data) { + PopulateTensor(alpha_, data); + } + std::vector GetOutput() { return ExtractVector(output_); } + + protected: + int input_; + int alpha_; + int output_; +}; + +TEST(FloatActivationsOpTest, PRelu) { + PReluOpModel m({TensorType_FLOAT32, {1, 2, 2, 3}}, + {TensorType_FLOAT32, {1, 1, 3}}); + + m.SetInput({ + 0.0f, 0.0f, 0.0f, // Row 1, Column 1 + 1.0f, 1.0f, 1.0f, // Row 1, Column 2 + -1.0f, -1.0f, -1.0f, // Row 2, Column 1 + -2.0f, -2.0f, -2.0f, // Row 1, Column 2 + }); + m.SetAlpha({0.0f, 1.0f, 2.0f}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({ + 0.0f, 0.0f, 0.0f, // Row 1, Column 1 + 1.0f, 1.0f, 1.0f, // Row 1, Column 2 + 0.0f, -1.0f, -2.0f, // Row 2, Column 1 + 0.0f, -2.0f, -4.0f, // Row 1, Column 2 + })); +} + } // namespace } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/register.cc b/tensorflow/contrib/lite/kernels/register.cc index 369d3b9886..62045f0a4d 100644 --- a/tensorflow/contrib/lite/kernels/register.cc +++ b/tensorflow/contrib/lite/kernels/register.cc @@ -75,6 +75,7 @@ TfLiteRegistration* Register_TOPK_V2(); TfLiteRegistration* Register_LOG_SOFTMAX(); TfLiteRegistration* Register_CAST(); TfLiteRegistration* Register_DEQUANTIZE(); +TfLiteRegistration* Register_PRELU(); BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_RELU, Register_RELU()); @@ -131,6 +132,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_LOG_SOFTMAX, Register_LOG_SOFTMAX()); AddBuiltin(BuiltinOperator_CAST, Register_CAST()); AddBuiltin(BuiltinOperator_DEQUANTIZE, Register_DEQUANTIZE()); + AddBuiltin(BuiltinOperator_PRELU, Register_PRELU()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc index 9c619f88e0..b7ccdf070b 100644 --- a/tensorflow/contrib/lite/model.cc +++ b/tensorflow/contrib/lite/model.cc @@ -309,6 +309,7 @@ void* ParseOpData(const Operator* op, BuiltinOperator op_type, case BuiltinOperator_LOG_SOFTMAX: case BuiltinOperator_CAST: case BuiltinOperator_DEQUANTIZE: + case BuiltinOperator_PRELU: break; case BuiltinOperator_LSH_PROJECTION: { TfLiteLSHProjectionParams* params = diff --git a/tensorflow/contrib/lite/nnapi_delegate.cc b/tensorflow/contrib/lite/nnapi_delegate.cc index 9d00d965d3..e31b7c03a5 100644 --- a/tensorflow/contrib/lite/nnapi_delegate.cc +++ b/tensorflow/contrib/lite/nnapi_delegate.cc @@ -349,6 +349,7 @@ void AddOpsAndParams(tflite::Interpreter* interpreter, case tflite::BuiltinOperator_DEQUANTIZE: case tflite::BuiltinOperator_DELEGATE: case tflite::BuiltinOperator_CAST: + case tflite::BuiltinOperator_PRELU: FATAL("Op code %d is currently not delegated to NNAPI", builtin); nn_op_type = -1; // set to invalid break; diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs index 04387fed33..e1075971e9 100644 --- a/tensorflow/contrib/lite/schema/schema.fbs +++ b/tensorflow/contrib/lite/schema/schema.fbs @@ -130,6 +130,7 @@ enum BuiltinOperator : byte { DELEGATE = 51, BIDIRECTIONAL_SEQUENCE_LSTM = 52, CAST = 53, + PRELU = 54, } // Options for the builtin operators. diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h index b922de2081..86daeaf5cc 100755 --- a/tensorflow/contrib/lite/schema/schema_generated.h +++ b/tensorflow/contrib/lite/schema/schema_generated.h @@ -254,11 +254,12 @@ enum BuiltinOperator { BuiltinOperator_DELEGATE = 51, BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM = 52, BuiltinOperator_CAST = 53, + BuiltinOperator_PRELU = 54, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_CAST + BuiltinOperator_MAX = BuiltinOperator_PRELU }; -inline BuiltinOperator (&EnumValuesBuiltinOperator())[52] { +inline BuiltinOperator (&EnumValuesBuiltinOperator())[53] { static BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -311,7 +312,8 @@ inline BuiltinOperator (&EnumValuesBuiltinOperator())[52] { BuiltinOperator_LOG_SOFTMAX, BuiltinOperator_DELEGATE, BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM, - BuiltinOperator_CAST + BuiltinOperator_CAST, + BuiltinOperator_PRELU }; return values; } @@ -372,6 +374,7 @@ inline const char **EnumNamesBuiltinOperator() { "DELEGATE", "BIDIRECTIONAL_SEQUENCE_LSTM", "CAST", + "PRELU", nullptr }; return names; diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index f1b18ad30f..555ea90034 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -39,6 +39,7 @@ gen_zipped_test_files( "mean.zip", "mul.zip", "pad.zip", + "prelu.zip", "relu.zip", "relu1.zip", "relu6.zip", diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py index 420bdb41f1..38de9dcf2c 100644 --- a/tensorflow/contrib/lite/testing/generate_examples.py +++ b/tensorflow/contrib/lite/testing/generate_examples.py @@ -617,6 +617,54 @@ def make_relu6_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +def make_prelu_tests(zip_path): + """Make a set of tests to do PReLU.""" + + test_parameters = [{ + # The canonical case for image processing is having a 4D `input` (NHWC) + # and `shared_axes`=[1, 2], so the alpha parameter is per channel. + "input_shape": [[1, 10, 10, 3], [3, 3, 3, 3]], + "shared_axes": [[1, 2], [1]], + }] + + def build_graph(parameters): + """Build the graph for the test case.""" + + input_tensor = tf.placeholder( + dtype=tf.float32, name="input", shape=parameters["input_shape"]) + prelu = tf.keras.layers.PReLU(shared_axes=parameters["shared_axes"]) + out = prelu(input_tensor) + return [input_tensor], [out] + + def build_inputs(parameters, sess, inputs, outputs): + """Build the inputs for the test case.""" + + input_shape = parameters["input_shape"] + input_values = create_tensor_data( + np.float32, input_shape, min_value=-10, max_value=10) + shared_axes = parameters["shared_axes"] + + alpha_shape = [] + for dim in range(1, len(input_shape)): + alpha_shape.append(1 if dim in shared_axes else input_shape[dim]) + + alpha_values = create_tensor_data(np.float32, alpha_shape) + + with tf.variable_scope("", reuse=True): + alpha = tf.get_variable("p_re_lu/alpha") + sess.run(alpha.assign(alpha_values)) + + return [input_values], sess.run( + outputs, feed_dict=dict(zip(inputs, [input_values]))) + + make_zip_of_tests( + zip_path, + test_parameters, + build_graph, + build_inputs, + use_frozen_graph=True) + + # This function tests various TensorFLow functions that generates Const op, # including `tf.ones`, `tf.zeros` and random functions. def make_constant_tests(zip_path): @@ -1911,6 +1959,7 @@ def main(unused_args): "relu.zip": make_relu_tests, "relu1.zip": make_relu1_tests, "relu6.zip": make_relu6_tests, + "prelu.zip": make_prelu_tests, "l2_pool.zip": make_pool_tests(make_l2_pool), "avg_pool.zip": make_pool_tests(tf.nn.avg_pool), "max_pool.zip": make_pool_tests(tf.nn.max_pool), diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc index 5e76e7c510..ba2d259462 100644 --- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc @@ -88,6 +88,9 @@ std::map kBrokenTests = { // Transpose only supports 1D-4D input tensors. {R"(^\/transpose.*input_shape=\[.,.,.,.,.\])", "71545879"}, + + // PRelu only supports 4D input with (1, 1, channels) 3D alpha now. + {R"(^\/prelu.*shared_axes=\[1\])", "75975192"}, }; // Allows test data to be unzipped into a temporary directory and makes @@ -253,6 +256,7 @@ INSTANTIATE_TESTS(mul) INSTANTIATE_TESTS(pad) INSTANTIATE_TESTS(relu) INSTANTIATE_TESTS(relu1) +INSTANTIATE_TESTS(prelu) INSTANTIATE_TESTS(relu6) INSTANTIATE_TESTS(reshape) INSTANTIATE_TESTS(resize_bilinear) diff --git a/tensorflow/contrib/lite/toco/BUILD b/tensorflow/contrib/lite/toco/BUILD index 395abc5326..486ff1edcd 100644 --- a/tensorflow/contrib/lite/toco/BUILD +++ b/tensorflow/contrib/lite/toco/BUILD @@ -193,6 +193,7 @@ cc_library( "graph_transformations/identify_lstm.cc", "graph_transformations/identify_lstm_merge_inputs.cc", "graph_transformations/identify_lstm_split_inputs.cc", + "graph_transformations/identify_prelu.cc", "graph_transformations/identify_relu1.cc", "graph_transformations/lstm_utils.cc", "graph_transformations/make_initial_dequantize_operator.cc", diff --git a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h index 11e5e19f50..640afc7c74 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h +++ b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h @@ -129,6 +129,7 @@ DECLARE_GRAPH_TRANSFORMATION(IdentifyLstmCell) DECLARE_GRAPH_TRANSFORMATION(SplitLstmCellInputs) DECLARE_GRAPH_TRANSFORMATION(MergeLstmCellInputs) DECLARE_GRAPH_TRANSFORMATION(IdentifyRelu1) +DECLARE_GRAPH_TRANSFORMATION(IdentifyPRelu) DECLARE_GRAPH_TRANSFORMATION(IdentifyDilatedConv) DECLARE_GRAPH_TRANSFORMATION(MakeInitialDequantizeOperator) DECLARE_GRAPH_TRANSFORMATION(PropagateActivationFunctionIntoConstants) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/identify_prelu.cc b/tensorflow/contrib/lite/toco/graph_transformations/identify_prelu.cc new file mode 100644 index 0000000000..30be4ac0aa --- /dev/null +++ b/tensorflow/contrib/lite/toco/graph_transformations/identify_prelu.cc @@ -0,0 +1,119 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include +#include +#include + +#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" +#include "tensorflow/contrib/lite/toco/model.h" +#include "tensorflow/contrib/lite/toco/tooling_util.h" +#include "tensorflow/core/platform/logging.h" + +// This transformation rule tries to identify the PRelu structure generated by +// Keras, and convert it to a single op. +// +// The formula of PReLU is: +// f(x) = alpha * x for x < 0, f(x) = x for x >= 0. +// +// `x` is the input, and `alpha` is a trainable tensor which can be broadcasted +// to the shape of `x`. +// +// There's no native PRelu op in TensorFlow, so Keras generates the following +// structure which does the equivalent calculation: +// f(x) = Relu(x) + (-alpha * Relu(-x)) +// +// Practically, alpha is always a constant in the inference graph, and Toco have +// other graph transformations which fold the activation functions to other ops. +// Therefore, we're looking for the structure: +// +// f(x) = Relu(x) + (negative_alpha * Neg(x, activation=Relu)) + +namespace toco { + +bool IdentifyPRelu::Run(Model* model, std::size_t op_index) { + const auto add_op_it = model->operators.begin() + op_index; + const auto* add_op = add_op_it->get(); + if (add_op == nullptr || add_op->type != OperatorType::kAdd || + add_op->inputs.size() != 2 || + add_op->fused_activation_function != FusedActivationFunctionType::kNone) { + return false; + } + + const auto* relu_input_op = GetOpWithOutput(*model, add_op->inputs[0]); + if (relu_input_op == nullptr || relu_input_op->type != OperatorType::kRelu || + relu_input_op->inputs.size() != 1 || + relu_input_op->fused_activation_function != + FusedActivationFunctionType::kNone) { + return false; + } + + // TODO(ycling): Both Add and Mul are commutative. Support the case where + // the position of operands are exchanged. + const auto* mul_op = GetOpWithOutput(*model, add_op->inputs[1]); + if (mul_op == nullptr || mul_op->type != OperatorType::kMul || + mul_op->inputs.size() != 2 || + mul_op->fused_activation_function != FusedActivationFunctionType::kNone) { + return false; + } + + const auto neg_alpha_tensor_name = mul_op->inputs[0]; + + const auto* relu_neg_input_op = GetOpWithOutput(*model, mul_op->inputs[1]); + + if (relu_neg_input_op == nullptr || + relu_neg_input_op->type != OperatorType::kNeg || + relu_neg_input_op->fused_activation_function != + FusedActivationFunctionType::kRelu || + relu_neg_input_op->inputs.size() != 1) { + return false; + } + + if (relu_input_op->inputs[0] != relu_neg_input_op->inputs[0]) { + return false; + } + + const auto input_tensor_name = relu_input_op->inputs[0]; + const auto output_tensor_name = add_op->outputs[0]; + + // Construct a tensor for positive alpha (double negative). + const auto alpha_tensor_name = + AvailableArrayName(*model, neg_alpha_tensor_name + "_neg"); + model->GetOrCreateArray(alpha_tensor_name); + + auto* neg_neg_alpha_op = new NegOperator; + neg_neg_alpha_op->inputs = {neg_alpha_tensor_name}; + neg_neg_alpha_op->outputs = {alpha_tensor_name}; + model->operators.emplace(add_op_it, neg_neg_alpha_op); + + auto* prelu_op = new PReluOperator; + prelu_op->inputs = {input_tensor_name, alpha_tensor_name}; + prelu_op->outputs = {output_tensor_name}; + model->operators.emplace(add_op_it, prelu_op); + AddMessageF("Creating %s replacing equivalent subgraph", LogName(*prelu_op)); + + DeleteArrayIfUsedOnce(neg_alpha_tensor_name, model); + DeleteArrayIfUsedOnce(add_op->inputs[0], model); + DeleteArrayIfUsedOnce(add_op->inputs[1], model); + DeleteArrayIfUsedOnce(mul_op->inputs[1], model); + // Remove the existing Add op that outputs the final result. If the other + // intermediate tensors aren't used by other ops, those will be removed by + // other graph transformation rules. + model->operators.erase(FindOp(*model, add_op)); + + return true; +} + +} // namespace toco diff --git a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc index 375848a7d4..676736cfc5 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1467,6 +1467,7 @@ bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) { case OperatorType::kRelu: case OperatorType::kRelu1: case OperatorType::kRelu6: + case OperatorType::kPRelu: case OperatorType::kSoftmax: case OperatorType::kLogSoftmax: case OperatorType::kLogistic: diff --git a/tensorflow/contrib/lite/toco/model.h b/tensorflow/contrib/lite/toco/model.h index 3fa0089cba..5199e292e1 100644 --- a/tensorflow/contrib/lite/toco/model.h +++ b/tensorflow/contrib/lite/toco/model.h @@ -65,6 +65,7 @@ enum class OperatorType { kRelu, kRelu1, kRelu6, + kPRelu, kSoftmax, kLogSoftmax, kSub, @@ -566,6 +567,18 @@ struct Relu6Operator : Operator { Relu6Operator() : Operator(OperatorType::kRelu6) {} }; +// PRelu +// f(x) = alpha * x for x < 0, f(x) = x for x >= 0. +// +// Inputs: +// inputs[0]: required: the input array +// inputs[1]: required: the alpha array +// +// Equivalent to keras.layers.PReLU. +struct PReluOperator : Operator { + PReluOperator() : Operator(OperatorType::kPRelu) {} +}; + // Element-wise Logistic operator: // x -> Logistic(x) = 1 / (1 + exp(-x)) // diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc index f2cc4ef71f..f23249cfa1 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator.cc @@ -854,6 +854,8 @@ std::vector> BuildOperatorList() { new SimpleOperator("RELU_N1_TO_1", OperatorType::kRelu1)); ops.emplace_back( new SimpleOperator("RELU6", OperatorType::kRelu6)); + ops.emplace_back( + new SimpleOperator("PRELU", OperatorType::kPRelu)); ops.emplace_back(new SimpleOperator( "LOGISTIC", OperatorType::kLogistic)); ops.emplace_back( diff --git a/tensorflow/contrib/lite/toco/toco_tooling.cc b/tensorflow/contrib/lite/toco/toco_tooling.cc index ca66110ba3..30dd6fab9e 100644 --- a/tensorflow/contrib/lite/toco/toco_tooling.cc +++ b/tensorflow/contrib/lite/toco/toco_tooling.cc @@ -94,6 +94,7 @@ void MakeGeneralGraphTransformationsSet( transformations->Add(new IdentifyL2Normalization); transformations->Add(new IdentifyL2Pool); transformations->Add(new IdentifyRelu1); + transformations->Add(new IdentifyPRelu); transformations->Add(new RemoveTrivialBinaryOperator); transformations->Add(new ReadFakeQuantMinMax); transformations->Add(new ResolveSpaceToBatchNDAttributes); diff --git a/tensorflow/contrib/lite/toco/tooling_util.cc b/tensorflow/contrib/lite/toco/tooling_util.cc index 2362206a14..ec1770c129 100644 --- a/tensorflow/contrib/lite/toco/tooling_util.cc +++ b/tensorflow/contrib/lite/toco/tooling_util.cc @@ -300,6 +300,7 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(Relu) HANDLE_OPERATORTYPENAME_CASE(Relu1) HANDLE_OPERATORTYPENAME_CASE(Relu6) + HANDLE_OPERATORTYPENAME_CASE(PRelu) HANDLE_OPERATORTYPENAME_CASE(ReorderAxes) HANDLE_OPERATORTYPENAME_CASE(Softmax) HANDLE_OPERATORTYPENAME_CASE(LogSoftmax) -- GitLab From 282750fee5e2df502436ca9ef6a95283f8adab34 Mon Sep 17 00:00:00 2001 From: Ayush Dubey Date: Thu, 22 Mar 2018 11:25:58 -0700 Subject: [PATCH 083/906] Add new Ops for ScopedAllocator and the associated Concat and Split. The ScopedAllocatorOp allocates a large backing tensor whose slices may be concatenated or splitted with ScopedAllocatorConcatOp and ScopedAllocatorSplitOp respectively. These ops should only be added via Grappler optimizations on the dataflow graph provided by the user. PiperOrigin-RevId: 190097586 --- tensorflow/core/BUILD | 3 + .../core/common_runtime/gpu/gpu_device.cc | 11 + .../core/common_runtime/gpu/gpu_device.h | 15 +- .../core/common_runtime/scoped_allocator.cc | 3 +- .../common_runtime/scoped_allocator_mgr.cc | 25 +- .../common_runtime/scoped_allocator_mgr.h | 6 +- .../scoped_allocator_mgr_test.cc | 25 +- .../core/common_runtime/threadpool_device.cc | 16 +- .../core/common_runtime/threadpool_device.h | 12 +- tensorflow/core/framework/allocator.h | 16 +- tensorflow/core/framework/device_base.h | 16 +- tensorflow/core/framework/op_kernel.cc | 9 +- tensorflow/core/kernels/BUILD | 37 +++ .../core/kernels/scoped_allocator_ops.cc | 216 +++++++++++++ .../core/kernels/scoped_allocator_ops_test.cc | 296 ++++++++++++++++++ tensorflow/core/ops/scoped_allocator_ops.cc | 81 +++++ 16 files changed, 742 insertions(+), 45 deletions(-) create mode 100644 tensorflow/core/kernels/scoped_allocator_ops.cc create mode 100644 tensorflow/core/kernels/scoped_allocator_ops_test.cc create mode 100644 tensorflow/core/ops/scoped_allocator_ops.cc diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index a14eeed1a5..15cbba8285 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -634,6 +634,7 @@ tf_gen_op_libs( "random_ops", "remote_fused_graph_ops", "resource_variable_ops", + "scoped_allocator_ops", "sdca_ops", "set_ops", "script_ops", @@ -717,6 +718,7 @@ cc_library( ":random_ops_op_lib", ":remote_fused_graph_ops_op_lib", ":resource_variable_ops_op_lib", + ":scoped_allocator_ops_op_lib", ":script_ops_op_lib", ":sdca_ops_op_lib", ":sendrecv_ops_op_lib", @@ -861,6 +863,7 @@ cc_library( "//tensorflow/core/kernels:remote_fused_graph_ops", "//tensorflow/core/kernels:required", "//tensorflow/core/kernels:resource_variable_ops", + "//tensorflow/core/kernels:scoped_allocator_ops", "//tensorflow/core/kernels:sdca_ops", "//tensorflow/core/kernels:set_kernels", "//tensorflow/core/kernels:sparse", diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 8357cc5a72..52fd20e479 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -840,6 +840,17 @@ void BaseGPUDevice::ReinitializeGpuDevice(OpKernelContext* context, } } +Allocator* BaseGPUDevice::GetScopedAllocator(AllocatorAttributes attr, + int64 step_id) { + if (attr.scope_id > 0) { + return scoped_allocator_mgr_->GetContainer(step_id)->GetInstance( + attr.scope_id); + } + LOG(FATAL) << "Unexpected call to BaseGPUDevice::GetScopedAllocator " + << "attr.scope_id = " << attr.scope_id; + return gpu_allocator_; +} + const int BaseGPUDeviceFactory::InterconnectMap::kSameDeviceStrength = 1000; const int BaseGPUDeviceFactory::InterconnectMap::kStreamExecutorStrength = 1; diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.h b/tensorflow/core/common_runtime/gpu/gpu_device.h index d817c7dd1f..cc5c3881dd 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.h +++ b/tensorflow/core/common_runtime/gpu/gpu_device.h @@ -17,8 +17,8 @@ limitations under the License. #error This file must only be included when building with Cuda support #endif -#ifndef TENSORFLOW_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ -#define TENSORFLOW_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ #include #include @@ -33,6 +33,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/gpu/gpu_id_utils.h" #include "tensorflow/core/common_runtime/gpu_device_context.h" #include "tensorflow/core/common_runtime/local_device.h" +#include "tensorflow/core/common_runtime/scoped_allocator_mgr.h" #include "tensorflow/core/framework/allocator.h" #include "tensorflow/core/framework/device_base.h" #include "tensorflow/core/framework/op_kernel.h" @@ -95,11 +96,19 @@ class BaseGPUDevice : public LocalDevice { // corresponds to the cuda context. gpu::StreamExecutor* executor() const { return executor_; } + Allocator* GetScopedAllocator(AllocatorAttributes attr, + int64 step_id) override; + + ScopedAllocatorMgr* GetScopedAllocatorMgr() const override { + return scoped_allocator_mgr_.get(); + } + protected: Allocator* gpu_allocator_; // not owned Allocator* cpu_allocator_; // not owned gpu::StreamExecutor* executor_; // not owned + std::unique_ptr scoped_allocator_mgr_; private: struct StreamGroup { @@ -205,4 +214,4 @@ class BaseGPUDeviceFactory : public DeviceFactory { } // namespace tensorflow -#endif // TENSORFLOW_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_GPU_GPU_DEVICE_H_ diff --git a/tensorflow/core/common_runtime/scoped_allocator.cc b/tensorflow/core/common_runtime/scoped_allocator.cc index 31e7a5e3e2..a26672b79d 100644 --- a/tensorflow/core/common_runtime/scoped_allocator.cc +++ b/tensorflow/core/common_runtime/scoped_allocator.cc @@ -75,7 +75,8 @@ void* ScopedAllocator::AllocateRaw(int32 field_index, size_t num_bytes) { if (num_bytes != f.bytes) { LOG(ERROR) << "ScopedAllocator " << name_ << " got request for " << num_bytes << " bytes from field " << field_index - << " which has precalculated size " << f.bytes; + << " which has precalculated size " << f.bytes << " and offset " + << f.offset; return nullptr; } diff --git a/tensorflow/core/common_runtime/scoped_allocator_mgr.cc b/tensorflow/core/common_runtime/scoped_allocator_mgr.cc index d0d05c6d1b..e1f70404e3 100644 --- a/tensorflow/core/common_runtime/scoped_allocator_mgr.cc +++ b/tensorflow/core/common_runtime/scoped_allocator_mgr.cc @@ -22,7 +22,7 @@ namespace tensorflow { Status ScopedAllocatorContainer::AddScopedAllocator( const Tensor& backing_tensor, int32 scope_id, const string& scope_name, const gtl::ArraySlice& fields, - int32 expected_call_count, ScopedAllocator** sa_ptr) { + int32 expected_call_count) { VLOG(1) << "AddScopedAllocator " << mgr_->device_name() << " step_id_=" << step_id_ << " scope_id=" << scope_id; mutex_lock l(mu_); @@ -41,17 +41,17 @@ Status ScopedAllocatorContainer::AddScopedAllocator( } } VLOG(2) << " container " << this << " step_id " << step_id_; - *sa_ptr = new ScopedAllocator(backing_tensor, scope_id, scope_name, fields, - expected_call_count, this); - allocators_[scope_id] = ScopedAllocatorContainer::SAField( - ScopedAllocator::kBackingIndex, *sa_ptr); + ScopedAllocator* sa = new ScopedAllocator( + backing_tensor, scope_id, scope_name, fields, expected_call_count, this); + allocators_[scope_id] = + ScopedAllocatorContainer::SAField(ScopedAllocator::kBackingIndex, sa); VLOG(2) << "#fields " << fields.size(); for (int i = 0; i < fields.size(); ++i) { const ScopedAllocator::Field& f = fields[i]; VLOG(2) << "Adding instance with for " << mgr_->device_name() << " scope_id=" << f.scope_id; allocators_[f.scope_id] = ScopedAllocatorContainer::SAField( - i, new ScopedAllocatorInstance(*sa_ptr, i)); + i, new ScopedAllocatorInstance(sa, i)); } return Status::OK(); } @@ -154,23 +154,26 @@ Status ScopedAllocatorMgr::AddScopedAllocator( const Tensor& backing_tensor, int64 step_id, int32 scope_id, const string& scope_name, const gtl::ArraySlice& fields, - int32 expected_call_count, ScopedAllocator** sa_ptr) { + int32 expected_call_count) { ScopedAllocatorContainer* sac = GetContainer(step_id); return sac->AddScopedAllocator(backing_tensor, scope_id, scope_name, fields, - expected_call_count, sa_ptr); + expected_call_count); } void ScopedAllocatorMgr::PopulateFields( - int32 scope_id, const gtl::ArraySlice& shapes, DataType dtype, - std::vector* fields) { + int32 scope_id, const gtl::ArraySlice& shapes, + const DataType dtype, std::vector* fields) { const int32 num_fields = static_cast(shapes.size()); fields->resize(num_fields); size_t offset = 0; for (int32 i = 0; i < num_fields; ++i) { - size_t bytes = shapes[i].num_elements() * sizeof(dtype); + size_t bytes = shapes[i].num_elements() * DataTypeSize(dtype); (*fields)[i].scope_id = scope_id + 1 + i; (*fields)[i].bytes = bytes; (*fields)[i].offset = offset; + VLOG(1) << "field=" << i << " scope_id=" << (*fields)[i].scope_id + << " bytes=" << (*fields)[i].bytes + << " offset=" << (*fields)[i].offset; offset += bytes; size_t overshoot = offset % Allocator::kAllocatorAlignment; if (overshoot > 0) { diff --git a/tensorflow/core/common_runtime/scoped_allocator_mgr.h b/tensorflow/core/common_runtime/scoped_allocator_mgr.h index 4d5bc23dd9..effc5f2d77 100644 --- a/tensorflow/core/common_runtime/scoped_allocator_mgr.h +++ b/tensorflow/core/common_runtime/scoped_allocator_mgr.h @@ -34,7 +34,7 @@ class ScopedAllocatorContainer : public core::RefCounted { Status AddScopedAllocator( const Tensor& backing_tensor, int32 scope_id, const string& scope_name, const gtl::ArraySlice& fields, - int32 expected_call_count, ScopedAllocator** sa_ptr); + int32 expected_call_count); ScopedAllocatorInstance* GetInstance(int32 scope_id); ScopedAllocator* GetAllocator(int32 scope_id); @@ -83,7 +83,7 @@ class ScopedAllocatorMgr { const Tensor& backing_tensor, int64 step_id, int32 scope_id, const string& scope_name, const gtl::ArraySlice& fields, - int32 expected_call_count, ScopedAllocator** sa_ptr); + int32 expected_call_count); void Cleanup(int64 step_id); @@ -91,7 +91,7 @@ class ScopedAllocatorMgr { // consecutive scope_id values following that of the base ScopedAllocator. static void PopulateFields(int32 scope_id, const gtl::ArraySlice& shapes, - DataType dtype, + const DataType dtype, std::vector* fields); const string& device_name() const { return device_name_; } diff --git a/tensorflow/core/common_runtime/scoped_allocator_mgr_test.cc b/tensorflow/core/common_runtime/scoped_allocator_mgr_test.cc index 81cb3e7979..38e07e47f2 100644 --- a/tensorflow/core/common_runtime/scoped_allocator_mgr_test.cc +++ b/tensorflow/core/common_runtime/scoped_allocator_mgr_test.cc @@ -25,7 +25,7 @@ namespace { class ScopedAllocatorMgrTest : public ::testing::Test { public: - ScopedAllocatorMgrTest() : sam_("CPU0"), sa_(nullptr) {} + ScopedAllocatorMgrTest() : sam_("CPU0") {} void InitTensor() { backing_tensor_ = Tensor(cpu_allocator(), DT_FLOAT, backing_tensor_shape_); @@ -42,7 +42,7 @@ class ScopedAllocatorMgrTest : public ::testing::Test { << " expected_use_count " << expected_use_count; return sam_.AddScopedAllocator(backing_tensor_, step_id_, scope_id, "tensor_shape_599", fields_, - expected_use_count, &sa_); + expected_use_count); } Status PrepScopedAllocatorMgr(int expected_use_count) { @@ -87,7 +87,6 @@ class ScopedAllocatorMgrTest : public ::testing::Test { std::vector fields_shapes_; std::vector fields_; ScopedAllocatorMgr sam_; - ScopedAllocator* sa_; const int step_id_ = 101; const int scope_id_ = 599; std::vector sa_instances_; @@ -138,9 +137,9 @@ TEST_F(ScopedAllocatorMgrTest, ContainerAddAllocator) { // Cleanup the instances by invoking allocate and deallocate. void* ptr0 = - sa_instances_[0]->AllocateRaw(0 /* alignment */, 512 * sizeof(DT_FLOAT)); + sa_instances_[0]->AllocateRaw(0 /* alignment */, 512 * sizeof(float)); void* ptr1 = - sa_instances_[1]->AllocateRaw(0 /* alignment */, 512 * sizeof(DT_FLOAT)); + sa_instances_[1]->AllocateRaw(0 /* alignment */, 512 * sizeof(float)); sa_instances_[0]->DeallocateRaw(ptr0); sa_instances_[1]->DeallocateRaw(ptr1); } @@ -153,7 +152,6 @@ TEST_F(ScopedAllocatorMgrTest, AllocatorSuccess) { fields_shapes_ = std::vector({{512}, {3, 3}, {2, 256}}); Status s = PrepScopedAllocatorMgr(3); other = sac->GetAllocator(scope_id_); - EXPECT_EQ(other, sa_); ScopedAllocatorInstance* inst0 = sac->GetInstance(scope_id_ + 1); char* ptr0 = static_cast(inst0->AllocateRaw(0, 512 * sizeof(float))); @@ -187,8 +185,7 @@ TEST_F(ScopedAllocatorMgrTest, AllocatorInitFail) { fields_.resize(1); fields_[0].scope_id = scope_id_ + 1; fields_[0].offset = 0; - fields_[0].bytes = - backing_tensor_shape_.num_elements() * 2 * sizeof(DT_FLOAT); + fields_[0].bytes = backing_tensor_shape_.num_elements() * 2 * sizeof(float); // fields[0].offset + fields[0].bytes is larger than the size of the backing // tensor, so this check should fail EXPECT_DEATH(Status s = AddScopedAllocator(1, scope_id_), ""); @@ -208,20 +205,20 @@ TEST_F(ScopedAllocatorMgrTest, AllocatorFail) { // so we need to explicitly delete the instances to avoid a memleak. SaveInstances(fields_shapes_.size()); - char* ptr0 = static_cast( - sa_instances_[0]->AllocateRaw(0, 512 * sizeof(DT_FLOAT))); + char* ptr0 = + static_cast(sa_instances_[0]->AllocateRaw(0, 512 * sizeof(float))); VLOG(2) << "Should fail because we deallocate ptr=" << static_cast(ptr0 + 8) << " which we never allocated."; EXPECT_DEATH(sa_instances_[0]->DeallocateRaw(ptr0 + 8), ""); VLOG(2) << "Should fail because we allocate smaller than the size of the " << "field."; - EXPECT_EQ(nullptr, sa_instances_[1]->AllocateRaw(0, 256 * sizeof(DT_FLOAT))); + EXPECT_EQ(nullptr, sa_instances_[1]->AllocateRaw(0, 256 * sizeof(float))); VLOG(2) << "Should fail because we allocate larger than the size of the " << "field."; - EXPECT_EQ(nullptr, sa_instances_[1]->AllocateRaw(0, 1024 * sizeof(DT_FLOAT))); - void* ptr1 = sa_instances_[1]->AllocateRaw(0, 512 * sizeof(DT_FLOAT)); + EXPECT_EQ(nullptr, sa_instances_[1]->AllocateRaw(0, 1024 * sizeof(float))); + void* ptr1 = sa_instances_[1]->AllocateRaw(0, 512 * sizeof(float)); VLOG(2) << "Should fail because we exceed expected_use_count."; - EXPECT_EQ(nullptr, sa_instances_[0]->AllocateRaw(0, 512 * sizeof(DT_FLOAT))); + EXPECT_EQ(nullptr, sa_instances_[0]->AllocateRaw(0, 512 * sizeof(float))); sa_instances_[0]->DeallocateRaw(ptr0); sa_instances_[1]->DeallocateRaw(ptr1); } diff --git a/tensorflow/core/common_runtime/threadpool_device.cc b/tensorflow/core/common_runtime/threadpool_device.cc index 5aa01376ab..6d8de6a3c0 100644 --- a/tensorflow/core/common_runtime/threadpool_device.cc +++ b/tensorflow/core/common_runtime/threadpool_device.cc @@ -16,6 +16,8 @@ limitations under the License. #include "tensorflow/core/common_runtime/threadpool_device.h" #include "tensorflow/core/common_runtime/local_device.h" +#include "tensorflow/core/common_runtime/scoped_allocator.h" +#include "tensorflow/core/common_runtime/scoped_allocator_mgr.h" #include "tensorflow/core/framework/allocator.h" #include "tensorflow/core/framework/allocator_registry.h" #include "tensorflow/core/framework/device_base.h" @@ -40,7 +42,8 @@ ThreadPoolDevice::ThreadPoolDevice(const SessionOptions& options, Allocator* allocator) : LocalDevice(options, Device::BuildDeviceAttributes( name, DEVICE_CPU, memory_limit, locality)), - allocator_(allocator) {} + allocator_(allocator), + scoped_allocator_mgr_(new ScopedAllocatorMgr(name)) {} ThreadPoolDevice::~ThreadPoolDevice() {} @@ -65,6 +68,17 @@ Allocator* ThreadPoolDevice::GetAllocator(AllocatorAttributes attr) { return allocator_; } +Allocator* ThreadPoolDevice::GetScopedAllocator(AllocatorAttributes attr, + int64 step_id) { + if (attr.scope_id > 0) { + return scoped_allocator_mgr_->GetContainer(step_id)->GetInstance( + attr.scope_id); + } + LOG(FATAL) << "Unexpected call to ThreadPoolDevice::GetScopedAllocator " + << "attr.scope_id = " << attr.scope_id; + return allocator_; +} + Status ThreadPoolDevice::MakeTensorFromProto( const TensorProto& tensor_proto, const AllocatorAttributes alloc_attrs, Tensor* tensor) { diff --git a/tensorflow/core/common_runtime/threadpool_device.h b/tensorflow/core/common_runtime/threadpool_device.h index 37cb745a0a..afc5d15ebc 100644 --- a/tensorflow/core/common_runtime/threadpool_device.h +++ b/tensorflow/core/common_runtime/threadpool_device.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ -#define TENSORFLOW_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/local_device.h" @@ -31,6 +31,11 @@ class ThreadPoolDevice : public LocalDevice { void Compute(OpKernel* op_kernel, OpKernelContext* context) override; Allocator* GetAllocator(AllocatorAttributes attr) override; + Allocator* GetScopedAllocator(AllocatorAttributes attr, + int64 step_id) override; + ScopedAllocatorMgr* GetScopedAllocatorMgr() const override { + return scoped_allocator_mgr_.get(); + } Status MakeTensorFromProto(const TensorProto& tensor_proto, const AllocatorAttributes alloc_attrs, Tensor* tensor) override; @@ -39,8 +44,9 @@ class ThreadPoolDevice : public LocalDevice { private: Allocator* allocator_; // Not owned + std::unique_ptr scoped_allocator_mgr_; }; } // namespace tensorflow -#endif // TENSORFLOW_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_THREADPOOL_DEVICE_H_ diff --git a/tensorflow/core/framework/allocator.h b/tensorflow/core/framework/allocator.h index 3ce1b61246..2c87156dca 100644 --- a/tensorflow/core/framework/allocator.h +++ b/tensorflow/core/framework/allocator.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_FRAMEWORK_ALLOCATOR_H_ -#define TENSORFLOW_FRAMEWORK_ALLOCATOR_H_ +#ifndef TENSORFLOW_CORE_FRAMEWORK_ALLOCATOR_H_ +#define TENSORFLOW_CORE_FRAMEWORK_ALLOCATOR_H_ #include @@ -359,7 +359,12 @@ struct AllocatorAttributes { bool nic_compatible() const { return value & (0x1 << 1); } void set_gpu_compatible(bool v) { value |= (static_cast(v) << 2); } bool gpu_compatible() const { return value & (0x1 << 2); } - void Merge(AllocatorAttributes other) { value |= other.value; } + void Merge(AllocatorAttributes other) { + value |= other.value; + scope_id = (scope_id > 0 && other.scope_id == 0) + ? scope_id + : ((scope_id == 0) ? other.scope_id : 0); + } // Returns true if the fields set in *this is a subset of or equal to // those set in other. bool IsEqualOrLessRestrictiveThan(const AllocatorAttributes& other) const { @@ -371,6 +376,9 @@ struct AllocatorAttributes { // upper 8 bits in device-specific ways, and ops implemented for those // devices are responsible for setting those 8 bits appropriately. uint32 value = 0; + // EXPERIMENTAL: If this is greater than zero, then allocation is delegated to + // a named special-purpose allocator on the same device. + int32 scope_id = 0; }; // Returns a trivial implementation of Allocator which uses the system @@ -396,4 +404,4 @@ class SubAllocator { } // namespace tensorflow -#endif // TENSORFLOW_FRAMEWORK_ALLOCATOR_H_ +#endif // TENSORFLOW_CORE_FRAMEWORK_ALLOCATOR_H_ diff --git a/tensorflow/core/framework/device_base.h b/tensorflow/core/framework/device_base.h index fb6d5c69e1..52b9077d8c 100644 --- a/tensorflow/core/framework/device_base.h +++ b/tensorflow/core/framework/device_base.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_FRAMEWORK_DEVICE_BASE_H_ -#define TENSORFLOW_FRAMEWORK_DEVICE_BASE_H_ +#ifndef TENSORFLOW_CORE_FRAMEWORK_DEVICE_BASE_H_ +#define TENSORFLOW_CORE_FRAMEWORK_DEVICE_BASE_H_ #include #include @@ -48,6 +48,7 @@ class Env; class EventMgr; class OpKernelContext; class ResourceMgr; +class ScopedAllocatorMgr; class TensorProto; namespace thread { @@ -179,6 +180,15 @@ class DeviceBase { return GetAllocator(attr); } + // Return an Allocator prepared for use in particular places by graph + // optimization + virtual Allocator* GetScopedAllocator(AllocatorAttributes attr, + int64 step_id) { + LOG(FATAL) << "Device does not implement GetScopedAllocator()"; + } + + virtual ScopedAllocatorMgr* GetScopedAllocatorMgr() const { return nullptr; } + virtual const Eigen::ThreadPoolDevice* eigen_cpu_device() { CHECK(eigen_cpu_device_ != nullptr); return eigen_cpu_device_; @@ -243,4 +253,4 @@ class DeviceBase { } // namespace tensorflow -#endif // TENSORFLOW_FRAMEWORK_DEVICE_BASE_H_ +#endif // TENSORFLOW_CORE_FRAMEWORK_DEVICE_BASE_H_ diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc index 8654437059..9ec1c213c3 100644 --- a/tensorflow/core/framework/op_kernel.cc +++ b/tensorflow/core/framework/op_kernel.cc @@ -282,8 +282,13 @@ OpKernelContext::~OpKernelContext() { } Allocator* OpKernelContext::get_allocator(AllocatorAttributes attr) { - Allocator* allocator = - params_->device->GetStepAllocator(attr, resource_manager()); + Allocator* allocator = nullptr; + if (attr.scope_id > 0) { + allocator = params_->device->GetScopedAllocator(attr, step_id()); + CHECK(allocator); + } else { + allocator = params_->device->GetStepAllocator(attr, resource_manager()); + } if (track_allocations()) { mutex_lock lock(mu_); for (const auto& wrapped : wrapped_allocators_) { diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 2e39f25fc1..f6137fb860 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -1666,6 +1666,43 @@ tf_kernel_library( ], ) +tf_kernel_library( + name = "scoped_allocator_ops", + prefix = "scoped_allocator_ops", + deps = [ + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:scoped_allocator_ops_op_lib", + ], +) + +tf_cuda_cc_test( + name = "scoped_allocator_ops_test", + srcs = ["scoped_allocator_ops_test.cc"], + linkstatic = tf_kernel_tests_linkstatic(), #Required for benchmarking + deps = [ + ":cwise_op", + ":dense_update_ops", + ":ops_testutil", + ":ops_util", + ":scoped_allocator_ops", + ":variable_ops", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:math_ops_op_lib", + "//tensorflow/core:proto_text", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_kernel_library( name = "session_ops", prefix = "session_ops", diff --git a/tensorflow/core/kernels/scoped_allocator_ops.cc b/tensorflow/core/kernels/scoped_allocator_ops.cc new file mode 100644 index 0000000000..d7b25ffad0 --- /dev/null +++ b/tensorflow/core/kernels/scoped_allocator_ops.cc @@ -0,0 +1,216 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/common_runtime/dma_helper.h" +#include "tensorflow/core/common_runtime/scoped_allocator.h" +#include "tensorflow/core/common_runtime/scoped_allocator_mgr.h" +#include "tensorflow/core/framework/allocator.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/core/status.h" + +namespace tensorflow { + +class ScopedAllocatorOp : public OpKernel { + public: + explicit ScopedAllocatorOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("T", &dtype_)); + OP_REQUIRES_OK(context, context->GetAttr("shapes", &shapes_)); + OP_REQUIRES_OK(context, context->GetAttr("sa_name", &name_)); + OP_REQUIRES_OK(context, context->GetAttr("id", &id_)); + OP_REQUIRES_OK(context, context->GetAttr("expected_call_count", + &expected_call_count_)); + device_ = context->device(); + // Precalculate the size of the backing tensor and the offsets of + // the subtensors to be allocated from it, taking into account + // alignment considerations. + ScopedAllocatorMgr::PopulateFields(id_, shapes_, dtype_, &fields_); + size_t num_bytes = fields_.back().offset + fields_.back().bytes; + num_elements_ = num_bytes / DataTypeSize(dtype_); + OP_REQUIRES(context, num_bytes % DataTypeSize(dtype_) == 0, + errors::InvalidArgument( + "Number of bytes ", num_bytes, + " must be divisible by size of datatype ", dtype_)); + } + + void Compute(OpKernelContext* context) override { + ScopedAllocatorMgr* sam = device_->GetScopedAllocatorMgr(); + if (!sam) { + context->SetStatus(errors::Internal( + "ScopedAllocatorMgr not supported on device ", device_->name())); + return; + } + Tensor* backing_tensor = nullptr; + AllocatorAttributes attr = context->output_alloc_attr(0); + Status s = + context->allocate_output(0, {num_elements_}, &backing_tensor, attr); + VLOG(1) << "_ScopedAllocatorOp new backing tensor size " + << backing_tensor->TotalBytes() << " num_elements_ " + << num_elements_ << " buffer " << DMAHelper::buffer(backing_tensor) + << " base addr " << DMAHelper::base(backing_tensor); + if (s.ok()) { + s = sam->AddScopedAllocator(*backing_tensor, context->step_id(), id_, + name_, fields_, expected_call_count_); + } + if (!s.ok()) { + context->SetStatus(s); + } + } + + private: + std::vector shapes_; + DataType dtype_; + int64 num_elements_; + std::vector fields_; + string name_; + int32 id_; + int32 expected_call_count_; + DeviceBase* device_; +}; + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocator").Device(DEVICE_CPU), + ScopedAllocatorOp); + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocator").Device(DEVICE_GPU), + ScopedAllocatorOp); + +class ScopedAllocatorConcatOp : public OpKernel { + public: + explicit ScopedAllocatorConcatOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("shape", &shape_)); + OP_REQUIRES_OK(context, context->GetAttr("T", &dtype_)); + // This stuff is just for debugging + OP_REQUIRES_OK(context, context->GetAttr("sa_name", &name_)); + OP_REQUIRES_OK(context, context->GetAttr("id", &id_)); + device_ = context->device(); + } + + void Compute(OpKernelContext* context) override { + const Tensor& backing_tensor = context->input(0); + // Check that type matches. + OP_REQUIRES( + context, backing_tensor.dtype() == dtype_, + errors::InvalidArgument("Backing tensor type ", backing_tensor.dtype(), + " does not match expected type ", dtype_)); + // Check that backing tensor is at least as large as the shape of the + // output. + OP_REQUIRES(context, backing_tensor.NumElements() >= shape_.num_elements(), + errors::InvalidArgument("Backing tensor num elements ", + backing_tensor.NumElements(), + " is not equal to expected ", + shape_.num_elements())); + VLOG(1) << "_ScopedAllocatorConcatOp outputting backing tensor at " + << DMAHelper::base(&backing_tensor); + Tensor backing_copy(backing_tensor); + context->set_output(0, backing_copy); + const TensorBuffer* backing_buf = DMAHelper::buffer(&backing_copy); + const void* backing_tensor_lb = backing_buf->data(); + const void* backing_tensor_ub = static_cast( + static_cast(backing_tensor_lb) + backing_buf->size()); + // Check that all inputs lie entirely within the backing tensor. + for (int i = 1; i < context->num_inputs(); ++i) { + const TensorBuffer* input_buf = DMAHelper::buffer(&context->input(i)); + const void* input_lb = input_buf->data(); + OP_REQUIRES( + context, input_lb >= backing_tensor_lb, + errors::InvalidArgument("Lower bound check fail for input ", i, + " to node ", context->op_kernel().name())); + const void* input_ub = static_cast( + static_cast(input_lb) + input_buf->size()); + OP_REQUIRES( + context, input_ub <= backing_tensor_ub, + errors::InvalidArgument("Upper bound check fail for input ", i, + " to node ", context->op_kernel().name())); + } + } + + private: + TensorShape shape_; + DataType dtype_; + string name_; + int32 id_; + DeviceBase* device_; +}; + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocatorConcat").Device(DEVICE_CPU), + ScopedAllocatorConcatOp); + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocatorConcat").Device(DEVICE_GPU), + ScopedAllocatorConcatOp); + +class ScopedAllocatorSplitOp : public OpKernel { + public: + explicit ScopedAllocatorSplitOp(OpKernelConstruction* context) + : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("T", &dtype_)); + // This stuff is just for debugging + OP_REQUIRES_OK(context, context->GetAttr("sa_name", &name_)); + OP_REQUIRES_OK(context, context->GetAttr("id", &id_)); + device_ = context->device(); + } + + void Compute(OpKernelContext* context) override { + Tensor backing_copy(context->input(0)); + // Check that type matches. + OP_REQUIRES( + context, backing_copy.dtype() == dtype_, + errors::InvalidArgument("Backing tensor type ", backing_copy.dtype(), + " does not match expected type ", dtype_)); + const TensorBuffer* backing_buf = DMAHelper::buffer(&backing_copy); + const void* backing_tensor_lb = backing_buf->data(); + const void* backing_tensor_ub = static_cast( + static_cast(backing_tensor_lb) + backing_buf->size()); + for (int i = 1; i < context->num_inputs(); ++i) { + VLOG(1) << "_ScopedAllocatorSplitOp assigning input " << i + << " to output " << i - 1 << " buf addr " + << DMAHelper::base(&context->input(i)); + Tensor copy(context->input(i)); + OP_REQUIRES( + context, copy.dtype() == dtype_, + errors::InvalidArgument("Input ", i, " tensor type ", copy.dtype(), + " does not match expected type ", dtype_)); + context->set_output(i - 1, copy); + const TensorBuffer* input_buf = DMAHelper::buffer(©); + const void* input_lb = input_buf->data(); + OP_REQUIRES( + context, input_lb >= backing_tensor_lb, + errors::InvalidArgument("Lower bound check fail for input ", i, + " to node ", context->op_kernel().name())); + const void* input_ub = static_cast( + static_cast(input_lb) + input_buf->size()); + OP_REQUIRES( + context, input_ub <= backing_tensor_ub, + errors::InvalidArgument("Upper bound check fail for input ", i, + " to node ", context->op_kernel().name())); + } + } + + private: + DataType dtype_; + string name_; + int32 id_; + DeviceBase* device_; +}; + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocatorSplit").Device(DEVICE_CPU), + ScopedAllocatorSplitOp); + +REGISTER_KERNEL_BUILDER(Name("_ScopedAllocatorSplit").Device(DEVICE_GPU), + ScopedAllocatorSplitOp); + +} // namespace tensorflow diff --git a/tensorflow/core/kernels/scoped_allocator_ops_test.cc b/tensorflow/core/kernels/scoped_allocator_ops_test.cc new file mode 100644 index 0000000000..3d36c8b7d4 --- /dev/null +++ b/tensorflow/core/kernels/scoped_allocator_ops_test.cc @@ -0,0 +1,296 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include "tensorflow/core/common_runtime/dma_helper.h" +#include "tensorflow/core/common_runtime/kernel_benchmark_testlib.h" +#include "tensorflow/core/common_runtime/scoped_allocator.h" +#include "tensorflow/core/common_runtime/scoped_allocator_mgr.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/framework/tensor_types.h" +#include "tensorflow/core/graph/graph.h" +#include "tensorflow/core/graph/node_builder.h" +#include "tensorflow/core/graph/testlib.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/platform/test_benchmark.h" +#include "tensorflow/core/platform/types.h" + +namespace tensorflow { + +class ScopedAllocatorOpTest : public OpsTestBase { + protected: + void MakeOp(const gtl::ArraySlice& shapes, DataType dtype, + const string& name, int32 id, int32 expected_call_count) { + TF_EXPECT_OK(NodeDefBuilder("scoped_allocator_op", "_ScopedAllocator") + .Attr("T", dtype) + .Attr("shapes", shapes) + .Attr("sa_name", name) + .Attr("id", id) + .Attr("expected_call_count", expected_call_count) + .Finalize(node_def())); + TF_EXPECT_OK(InitOp()); + TF_ASSERT_OK(RunOpKernel()); + + // Allocate and Deallocate the tensors so that memory is not leaked + AllocatorAttributes attr; + Allocator* allocator; + for (size_t i = 0; i < shapes.size(); i++) { + attr.scope_id = id + i + 1; + allocator = device_->GetScopedAllocator(attr, context_->step_id()); + Tensor temp(allocator, dtype, shapes[i]); + } + } +}; + +TEST_F(ScopedAllocatorOpTest, Simple) { + MakeOp({TensorShape({8})}, DT_FLOAT, "test", 120, 1); + MakeOp({TensorShape({32, 32})}, DT_DOUBLE, "test1", 130, 1); + MakeOp({TensorShape({64}), TensorShape({3, 3}), TensorShape({5, 5, 5})}, + DT_HALF, "test2", 140, 3); + MakeOp({TensorShape({512}), TensorShape({64, 8})}, DT_UINT32, "test3", 150, + 2); +} + +// PrepOp is common to ConcatOp tests and SplitOpTests. +// It allocates a backing tensor that is large enough to hold all slices defined +// by fields, creates ScopedAllocatorInstances for each field, allocates the +// tensors, and assigns them as inputs to the op. +// We won't use the AddInput* suite of functions from ops_testutil.h because +// they allocate new tensors for each input. We need to mimic what a +// ScopedAllocator would do. +void PrepOp(DataType dtype, int32 id, + const std::vector& fields_shapes, + std::vector* fields, + Tensor** backing_tensor, Allocator* allocator, + ScopedAllocatorMgr* sam, const string& op_name, + std::vector* tensors, + gtl::InlinedVector* inputs, + const DataTypeVector& input_types) { + ScopedAllocatorMgr::PopulateFields(id, fields_shapes, dtype, fields); + // We don't simply allocate a tensor with shape as backing_tensor_shape, + // because we need to account for padding in the fields. We actually need a + // tensor of size at least (fields[-1].offset + fields[-1].bytes). + size_t num_bytes = fields->back().offset + fields->back().bytes; + int32_t num_elements = num_bytes / DataTypeSize(dtype); + CHECK_EQ(num_bytes % DataTypeSize(dtype), 0); + + *backing_tensor = new Tensor(allocator, dtype, {num_elements}); + int64 step_id = 10; + Status s = sam->AddScopedAllocator(**backing_tensor, step_id, id, + "sa_" + op_name + "_test", *fields, + fields_shapes.size()); + TF_ASSERT_OK(s); + + ScopedAllocatorContainer* sac = sam->GetContainer(step_id); + std::vector sa_instances(fields_shapes.size(), + nullptr); + for (size_t i = 0; i < fields_shapes.size(); i++) { + sa_instances[i] = sac->GetInstance(id + i + 1); + tensors->push_back(Tensor(sa_instances[i], dtype, fields_shapes[i])); + } + // Now add the tensor as an input to ScopedAllocatorOp. + // Order matters here, so first add the backing tensor, then the slices. + inputs->reserve(1 + tensors->size()); + CHECK_GT(input_types.size(), inputs->size()); + CHECK_EQ(input_types[inputs->size()], dtype); + inputs->push_back({nullptr, *backing_tensor}); + for (size_t i = 0; i < tensors->size(); i++) { + CHECK_EQ(input_types[inputs->size()], dtype); + inputs->push_back({nullptr, &((*tensors)[i])}); + } +} + +class ScopedAllocatorConcatOpTest : public OpsTestBase { + protected: + void MakeOp(const TensorShape& shape, DataType dtype, const string& name, + int32 id, int32 num_tensors) { + TF_EXPECT_OK( + NodeDefBuilder("scoped_allocator_concat_op", "_ScopedAllocatorConcat") + .Attr("shape", shape) + .Attr("T", dtype) + .Attr("N", num_tensors) + .Attr("sa_name", name) + .Attr("id", id) + .Input(FakeInput(dtype)) // backing tensor + .Input(FakeInput(num_tensors, dtype)) // list of tensors + .Finalize(node_def())); + TF_EXPECT_OK(InitOp()); + } + + void ExecOp(DataType dtype, int32 id, + const std::vector& fields_shapes) { + Tensor* backing_tensor = nullptr; + std::vector tensors; + std::vector fields; + PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(), + device_->GetScopedAllocatorMgr(), "split", &tensors, &inputs_, + input_types_); + + TF_ASSERT_OK(RunOpKernel()); + + // Check input and output are same tensor. + const Tensor& input = context_->input(0); + OpOutputList output_list; + Status s = context_->output_list("output", &output_list); + TF_ASSERT_OK(s); + const Tensor& output = *(output_list[0]); + CHECK_EQ(DMAHelper::base(&input), DMAHelper::base(&output)); + CHECK_EQ(input.dtype(), output.dtype()); + CHECK_EQ(input.NumElements(), output.NumElements()); + + // Free the backing tensor which was allocated in PrepOp. + delete backing_tensor; + } +}; + +TEST_F(ScopedAllocatorConcatOpTest, Success1) { + MakeOp({32}, DT_FLOAT, "test", 120, 2); + ExecOp(DT_FLOAT, 120, {{16}, {16}}); +} + +TEST_F(ScopedAllocatorConcatOpTest, Success2) { + MakeOp({2, 2, 2}, DT_DOUBLE, "test", 120, 2); + ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}}); +} + +TEST_F(ScopedAllocatorConcatOpTest, Success3) { + MakeOp({3, 3, 3}, DT_HALF, "test", 120, 3); + ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}}); +} + +TEST_F(ScopedAllocatorConcatOpTest, FailDtypeCheck) { + MakeOp({8}, DT_FLOAT, "test", 120, 2); + EXPECT_DEATH(ExecOp(DT_DOUBLE, 120, {{4}, {4}}), ""); +} + +TEST_F(ScopedAllocatorConcatOpTest, FailNumElementsCheck) { + MakeOp({32}, DT_FLOAT, "test", 120, 2); + AddInputFromArray({8}, {0, 1, 2, 3, 4, 5, 6, 7}); + AddInputFromArray({4}, {0, 1, 2, 3}); + AddInputFromArray({4}, {4, 5, 6, 7}); + Status s = RunOpKernel(); + EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); +} + +// This test should fail because the backing tensor and the input tensors are +// unrelated, i.e. the inputs are not slices of the backing tensor. +TEST_F(ScopedAllocatorConcatOpTest, FailBounds) { + MakeOp({8}, DT_DOUBLE, "test", 120, 2); + AddInputFromArray({8}, {0, 1, 2, 3, 4, 5, 6, 7}); + AddInputFromArray({4}, {0, 1, 2, 3}); + AddInputFromArray({4}, {4, 5, 6, 7}); + Status s = RunOpKernel(); + EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); +} + +class ScopedAllocatorSplitOpTest : public OpsTestBase { + protected: + void BuildNodeDef(const TensorShape& shape, DataType dtype, + const string& name, int32 id, int32 num_tensors) { + TF_EXPECT_OK( + NodeDefBuilder("scoped_allocator_split_op", "_ScopedAllocatorSplit") + .Attr("T", dtype) + .Attr("N", num_tensors) + .Attr("sa_name", name) + .Attr("id", id) + .Input(FakeInput(dtype)) // backing tensor and input + .Input( + FakeInput(num_tensors, dtype)) // list of subtensors to forward + .Finalize(node_def())); + } + + void MakeOp(const TensorShape& shape, DataType dtype, const string& name, + int32 id, int32 num_tensors) { + BuildNodeDef(shape, dtype, name, id, num_tensors); + TF_EXPECT_OK(InitOp()); + } + + // Similar to ConcatOpTest, we add inputs that are allocated from + // ScopedAllocator so that the memory lines up nicely. + void ExecOp(DataType dtype, int32 id, + const std::vector& fields_shapes) { + Tensor* backing_tensor = nullptr; + std::vector tensors; + std::vector fields; + PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(), + device_->GetScopedAllocatorMgr(), "split", &tensors, &inputs_, + input_types_); + + TF_ASSERT_OK(RunOpKernel()); + + // Check that outputs are slices of backing tensor. + const Tensor& input = context_->input(0); + const void* lower_limit = DMAHelper::base(&input); + const char* lower_limit_c = + static_cast(lower_limit); // for pointer arithmetic + OpOutputList output_list; + Status s = context_->output_list("output", &output_list); + TF_ASSERT_OK(s); + for (int i = 0; i < output_list.size(); i++) { + const Tensor& output = *(output_list[i]); + const void* expected_base = + static_cast(lower_limit_c + fields[i].offset); + CHECK_EQ(output.dtype(), input.dtype()); + CHECK_EQ(expected_base, DMAHelper::base(&output)); + CHECK_EQ(output.NumElements(), fields_shapes[i].num_elements()); + } + + // Free the backing tensor which was allocated in PrepOp. + delete backing_tensor; + } +}; + +TEST_F(ScopedAllocatorSplitOpTest, Success1) { + MakeOp({32}, DT_FLOAT, "test", 120, 2); + ExecOp(DT_FLOAT, 120, {{16}, {16}}); +} + +TEST_F(ScopedAllocatorSplitOpTest, Success2) { + MakeOp({2, 2, 2}, DT_DOUBLE, "test", 120, 2); + ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}}); +} + +TEST_F(ScopedAllocatorSplitOpTest, Success3) { + MakeOp({3, 3, 3}, DT_HALF, "test", 120, 3); + ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}}); +} + +TEST_F(ScopedAllocatorSplitOpTest, FailNLessThan2) { + BuildNodeDef({4, 4}, DT_FLOAT, "test", 120, 1); + Status s = InitOp(); + EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); +} + +TEST_F(ScopedAllocatorSplitOpTest, FailDtypeCheck) { + MakeOp({8}, DT_FLOAT, "test", 120, 2); + EXPECT_DEATH(ExecOp(DT_HALF, 120, {{4}, {4}}), ""); +} + +TEST_F(ScopedAllocatorSplitOpTest, FailBounds) { + MakeOp({8}, DT_DOUBLE, "test", 120, 2); + AddInputFromArray({8}, {0, 1, 2, 3, 4, 5, 6, 7}); + AddInputFromArray({4}, {0, 1, 2, 3}); + AddInputFromArray({4}, {4, 5, 6, 7}); + Status s = RunOpKernel(); + EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); +} + +} // end namespace tensorflow diff --git a/tensorflow/core/ops/scoped_allocator_ops.cc b/tensorflow/core/ops/scoped_allocator_ops.cc new file mode 100644 index 0000000000..f053a53f4c --- /dev/null +++ b/tensorflow/core/ops/scoped_allocator_ops.cc @@ -0,0 +1,81 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/common_shape_fns.h" +#include "tensorflow/core/framework/op.h" + +namespace tensorflow { + +REGISTER_OP("_ScopedAllocator") + .Output("output: T") + .Attr("shapes: list(shape)") + .Attr("T: type") + .Attr("sa_name: string") + .Attr("id: int") + .Attr("expected_call_count: int") + .SetIsStateful() + .SetShapeFn(shape_inference::ExplicitShape) + .Doc(R"doc( +Allocates a mutable tensor that becomes available to appropriately annotated +downstream Ops as backing store for their output tensor allocations via the +ScopedAllocatorMgr. +Returns a reference to this value. + +This is an experimental op for internal use only. It is possible to use this +op in unsafe ways. +)doc"); + +REGISTER_OP("_ScopedAllocatorConcat") + .Output("output: T") + .Input("backing: T") + .Input("inputs: N * T") + .Attr("shape: shape") + .Attr("T: type") + .Attr("sa_name: string") + .Attr("id: int") + .Attr("N: int >= 2") + .SetIsStateful() + .SetShapeFn(shape_inference::ExplicitShape) + .Doc(R"doc( +Acts like a Concat Op that merges multple tensors into one, however it must +only be used in conjunction with a ScopedAllocator which is backing the memory +of all of its input tensors so that actually it just outputs a read-only +reference to that ScopedAllocator's backing tensor. + +This is an experimental op for internal use only. It is possible to use this +op in unsafe ways. +)doc"); + +REGISTER_OP("_ScopedAllocatorSplit") + .Output("output: N * T") + .Input("concat: T") + .Input("split: N * T") + .Attr("T: type") + .Attr("sa_name: string") + .Attr("id: int") + .Attr("N: int >= 2") + .SetIsStateful() + .SetShapeFn(shape_inference::ExplicitShape) + .Doc(R"doc( +Acts like a Concat Op that merges multple tensors into one, however it must +only be used in conjunction with a ScopedAllocator which is backing the memory +of all of its input tensors so that actually it just outputs a read-only +reference to that ScopedAllocator's backing tensor. + +This is an experimental op for internal use only. It is possible to use this +op in unsafe ways. +)doc"); + +} // end namespace tensorflow -- GitLab From 5f50c1ea7d62d12253b56030110e68c8c1e87e7c Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Thu, 22 Mar 2018 11:29:25 -0700 Subject: [PATCH 084/906] Cleanup: replace an errant `in_eager_mode()` with `executing_eagerly()`. PiperOrigin-RevId: 190098277 --- tensorflow/python/framework/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index b3fa39fdab..de222e1932 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -5411,7 +5411,7 @@ def get_name_scope(): Returns: A string representing the current name scope. """ - if context.in_eager_mode(): + if context.executing_eagerly(): return context.context().scope_name.rstrip("/") return get_default_graph().get_name_scope() -- GitLab From ebf8abdb4db1f4224ba61cd1d478e5301ff4bfd7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 11:55:11 -0700 Subject: [PATCH 085/906] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 190102805 --- tensorflow/go/op/wrappers.go | 2112 +++++++++++++++++----------------- 1 file changed, 1056 insertions(+), 1056 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 16472464db..92370c4f95 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -605,75 +605,123 @@ func ExtractImagePatches(scope *Scope, images tf.Output, ksizes []int64, strides return op.Output(0) } -// MapPeekAttr is an optional argument to MapPeek. -type MapPeekAttr func(optionalAttr) +// SpaceToDepthAttr is an optional argument to SpaceToDepth. +type SpaceToDepthAttr func(optionalAttr) -// MapPeekCapacity sets the optional capacity attribute to value. -// If not specified, defaults to 0 -// -// REQUIRES: value >= 0 -func MapPeekCapacity(value int64) MapPeekAttr { +// SpaceToDepthDataFormat sets the optional data_format attribute to value. +// If not specified, defaults to "NHWC" +func SpaceToDepthDataFormat(value string) SpaceToDepthAttr { return func(m optionalAttr) { - m["capacity"] = value + m["data_format"] = value } } -// MapPeekMemoryLimit sets the optional memory_limit attribute to value. -// If not specified, defaults to 0 +// SpaceToDepth for tensors of type T. // -// REQUIRES: value >= 0 -func MapPeekMemoryLimit(value int64) MapPeekAttr { - return func(m optionalAttr) { - m["memory_limit"] = value - } -} - -// MapPeekContainer sets the optional container attribute to value. -// If not specified, defaults to "" -func MapPeekContainer(value string) MapPeekAttr { - return func(m optionalAttr) { - m["container"] = value - } -} - -// MapPeekSharedName sets the optional shared_name attribute to value. -// If not specified, defaults to "" -func MapPeekSharedName(value string) MapPeekAttr { - return func(m optionalAttr) { - m["shared_name"] = value - } -} - -// Op peeks at the values at the specified key. If the +// Rearranges blocks of spatial data, into depth. More specifically, +// this op outputs a copy of the input tensor where values from the `height` +// and `width` dimensions are moved to the `depth` dimension. +// The attr `block_size` indicates the input block size. // -// underlying container does not contain this key -// this op will block until it does. -func MapPeek(scope *Scope, key tf.Output, indices tf.Output, dtypes []tf.DataType, optional ...MapPeekAttr) (values []tf.Output) { +// * Non-overlapping blocks of size `block_size x block size` are rearranged +// into depth at each location. +// * The depth of the output tensor is `block_size * block_size * input_depth`. +// * The Y, X coordinates within each block of the input become the high order +// component of the output channel index. +// * The input tensor's height and width must be divisible by block_size. +// +// The `data_format` attr specifies the layout of the input and output tensors +// with the following options: +// "NHWC": `[ batch, height, width, channels ]` +// "NCHW": `[ batch, channels, height, width ]` +// "NCHW_VECT_C": +// `qint8 [ batch, channels / 4, height, width, 4 ]` +// +// It is useful to consider the operation as transforming a 6-D Tensor. +// e.g. for data_format = NHWC, +// Each element in the input tensor can be specified via 6 coordinates, +// ordered by decreasing memory layout significance as: +// n,oY,bY,oX,bX,iC (where n=batch index, oX, oY means X or Y coordinates +// within the output image, bX, bY means coordinates +// within the input block, iC means input channels). +// The output would be a transpose to the following layout: +// n,oY,oX,bY,bX,iC +// +// This operation is useful for resizing the activations between convolutions +// (but keeping all data), e.g. instead of pooling. It is also useful for training +// purely convolutional models. +// +// For example, given an input of shape `[1, 2, 2, 1]`, data_format = "NHWC" and +// block_size = 2: +// +// ``` +// x = [[[[1], [2]], +// [[3], [4]]]] +// ``` +// +// This operation will output a tensor of shape `[1, 1, 1, 4]`: +// +// ``` +// [[[[1, 2, 3, 4]]]] +// ``` +// +// Here, the input has a batch of 1 and each batch element has shape `[2, 2, 1]`, +// the corresponding output will have a single element (i.e. width and height are +// both 1) and will have a depth of 4 channels (1 * block_size * block_size). +// The output element shape is `[1, 1, 4]`. +// +// For an input tensor with larger depth, here of shape `[1, 2, 2, 3]`, e.g. +// +// ``` +// x = [[[[1, 2, 3], [4, 5, 6]], +// [[7, 8, 9], [10, 11, 12]]]] +// ``` +// +// This operation, for block_size of 2, will return the following tensor of shape +// `[1, 1, 1, 12]` +// +// ``` +// [[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]]] +// ``` +// +// Similarly, for the following input of shape `[1 4 4 1]`, and a block size of 2: +// +// ``` +// x = [[[[1], [2], [5], [6]], +// [[3], [4], [7], [8]], +// [[9], [10], [13], [14]], +// [[11], [12], [15], [16]]]] +// ``` +// +// the operator will return the following tensor of shape `[1 2 2 4]`: +// +// ``` +// x = [[[[1, 2, 3, 4], +// [5, 6, 7, 8]], +// [[9, 10, 11, 12], +// [13, 14, 15, 16]]]] +// ``` +// +// Arguments: +// +// block_size: The size of the spatial block. +func SpaceToDepth(scope *Scope, input tf.Output, block_size int64, optional ...SpaceToDepthAttr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtypes": dtypes} + attrs := map[string]interface{}{"block_size": block_size} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "MapPeek", + Type: "SpaceToDepth", Input: []tf.Input{ - key, indices, + input, }, Attrs: attrs, } op := scope.AddOperation(opspec) - if scope.Err() != nil { - return - } - var idx int - var err error - if values, idx, err = makeOutputList(op, idx, "values"); err != nil { - scope.UpdateErr("MapPeek", err) - return - } - return values + return op.Output(0) } // Returns (x - y)(x - y) element-wise. @@ -3383,45 +3431,6 @@ func MatrixDiag(scope *Scope, diagonal tf.Output) (output tf.Output) { return op.Output(0) } -// Says whether the targets are in the top `K` predictions. -// -// This outputs a `batch_size` bool array, an entry `out[i]` is `true` if the -// prediction for the target class is among the top `k` predictions among -// all predictions for example `i`. Note that the behavior of `InTopK` differs -// from the `TopK` op in its handling of ties; if multiple classes have the -// same prediction value and straddle the top-`k` boundary, all of those -// classes are considered to be in the top `k`. -// -// More formally, let -// -// \\(predictions_i\\) be the predictions for all classes for example `i`, -// \\(targets_i\\) be the target class for example `i`, -// \\(out_i\\) be the output for example `i`, -// -// $$out_i = predictions_{i, targets_i} \in TopKIncludingTies(predictions_i)$$ -// -// Arguments: -// predictions: A `batch_size` x `classes` tensor. -// targets: A `batch_size` vector of class ids. -// k: Number of top elements to look at for computing precision. -// -// Returns Computed Precision at `k` as a `bool Tensor`. -func InTopK(scope *Scope, predictions tf.Output, targets tf.Output, k int64) (precision tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"k": k} - opspec := tf.OpSpec{ - Type: "InTopK", - Input: []tf.Input{ - predictions, targets, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Given a quantized tensor described by (input, input_min, input_max), outputs a // // range that covers the actual values present in that tensor. This op is @@ -5092,45 +5101,46 @@ func FusedBatchNormV2(scope *Scope, x tf.Output, scale tf.Output, offset tf.Outp return op.Output(0), op.Output(1), op.Output(2), op.Output(3), op.Output(4) } -// TensorArrayGatherV3Attr is an optional argument to TensorArrayGatherV3. -type TensorArrayGatherV3Attr func(optionalAttr) +// AvgPoolGradAttr is an optional argument to AvgPoolGrad. +type AvgPoolGradAttr func(optionalAttr) -// TensorArrayGatherV3ElementShape sets the optional element_shape attribute to value. +// AvgPoolGradDataFormat sets the optional data_format attribute to value. // -// value: The expected shape of an element, if known. Used to -// validate the shapes of TensorArray elements. If this shape is not -// fully specified, gathering zero-size TensorArrays is an error. -// If not specified, defaults to -func TensorArrayGatherV3ElementShape(value tf.Shape) TensorArrayGatherV3Attr { +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the data is stored in the order of: +// [batch, in_height, in_width, in_channels]. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, in_channels, in_height, in_width]. +// If not specified, defaults to "NHWC" +func AvgPoolGradDataFormat(value string) AvgPoolGradAttr { return func(m optionalAttr) { - m["element_shape"] = value + m["data_format"] = value } } -// Gather specific elements from the TensorArray into output `value`. -// -// All elements selected by `indices` must have the same shape. +// Computes gradients of the average pooling function. // // Arguments: -// handle: The handle to a TensorArray. -// indices: The locations in the TensorArray from which to read tensor elements. -// flow_in: A float scalar that enforces proper chaining of operations. -// dtype: The type of the elem that is returned. +// orig_input_shape: 1-D. Shape of the original input to `avg_pool`. +// grad: 4-D with shape `[batch, height, width, channels]`. Gradients w.r.t. +// the output of `avg_pool`. +// ksize: The size of the sliding window for each dimension of the input. +// strides: The stride of the sliding window for each dimension of the input. +// padding: The type of padding algorithm to use. // -// Returns All of the elements in the TensorArray, concatenated along a new -// axis (the new dimension 0). -func TensorArrayGatherV3(scope *Scope, handle tf.Output, indices tf.Output, flow_in tf.Output, dtype tf.DataType, optional ...TensorArrayGatherV3Attr) (value tf.Output) { +// Returns 4-D. Gradients w.r.t. the input of `avg_pool`. +func AvgPoolGrad(scope *Scope, orig_input_shape tf.Output, grad tf.Output, ksize []int64, strides []int64, padding string, optional ...AvgPoolGradAttr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtype": dtype} + attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "TensorArrayGatherV3", + Type: "AvgPoolGrad", Input: []tf.Input{ - handle, indices, flow_in, + orig_input_shape, grad, }, Attrs: attrs, } @@ -5138,18 +5148,181 @@ func TensorArrayGatherV3(scope *Scope, handle tf.Output, indices tf.Output, flow return op.Output(0) } -// Converts each string in the input Tensor to its hash mod by a number of buckets. +// StageClearAttr is an optional argument to StageClear. +type StageClearAttr func(optionalAttr) + +// StageClearCapacity sets the optional capacity attribute to value. +// If not specified, defaults to 0 // -// The hash function is deterministic on the content of the string within the -// process and will never change. However, it is not suitable for cryptography. -// This function may be used when CPU time is scarce and inputs are trusted or -// unimportant. There is a risk of adversaries constructing inputs that all hash -// to the same bucket. To prevent this problem, use a strong hash function with -// `tf.string_to_hash_bucket_strong`. +// REQUIRES: value >= 0 +func StageClearCapacity(value int64) StageClearAttr { + return func(m optionalAttr) { + m["capacity"] = value + } +} + +// StageClearMemoryLimit sets the optional memory_limit attribute to value. +// If not specified, defaults to 0 // -// Arguments: -// input: The strings to assign a hash bucket. -// num_buckets: The number of buckets. +// REQUIRES: value >= 0 +func StageClearMemoryLimit(value int64) StageClearAttr { + return func(m optionalAttr) { + m["memory_limit"] = value + } +} + +// StageClearContainer sets the optional container attribute to value. +// If not specified, defaults to "" +func StageClearContainer(value string) StageClearAttr { + return func(m optionalAttr) { + m["container"] = value + } +} + +// StageClearSharedName sets the optional shared_name attribute to value. +// If not specified, defaults to "" +func StageClearSharedName(value string) StageClearAttr { + return func(m optionalAttr) { + m["shared_name"] = value + } +} + +// Op removes all elements in the underlying container. +// +// Returns the created operation. +func StageClear(scope *Scope, dtypes []tf.DataType, optional ...StageClearAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"dtypes": dtypes} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "StageClear", + + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + +// ComputeAccidentalHitsAttr is an optional argument to ComputeAccidentalHits. +type ComputeAccidentalHitsAttr func(optionalAttr) + +// ComputeAccidentalHitsSeed sets the optional seed attribute to value. +// +// value: If either seed or seed2 are set to be non-zero, the random number +// generator is seeded by the given seed. Otherwise, it is seeded by a +// random seed. +// If not specified, defaults to 0 +func ComputeAccidentalHitsSeed(value int64) ComputeAccidentalHitsAttr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// ComputeAccidentalHitsSeed2 sets the optional seed2 attribute to value. +// +// value: An second seed to avoid seed collision. +// If not specified, defaults to 0 +func ComputeAccidentalHitsSeed2(value int64) ComputeAccidentalHitsAttr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// Computes the ids of the positions in sampled_candidates that match true_labels. +// +// When doing log-odds NCE, the result of this op should be passed through a +// SparseToDense op, then added to the logits of the sampled candidates. This has +// the effect of 'removing' the sampled labels that match the true labels by +// making the classifier sure that they are sampled labels. +// +// Arguments: +// true_classes: The true_classes output of UnpackSparseLabels. +// sampled_candidates: The sampled_candidates output of CandidateSampler. +// num_true: Number of true labels per context. +// +// Returns A vector of indices corresponding to rows of true_candidates.A vector of IDs of positions in sampled_candidates that match a true_label +// for the row with the corresponding index in indices.A vector of the same length as indices and ids, in which each element +// is -FLOAT_MAX. +func ComputeAccidentalHits(scope *Scope, true_classes tf.Output, sampled_candidates tf.Output, num_true int64, optional ...ComputeAccidentalHitsAttr) (indices tf.Output, ids tf.Output, weights tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"num_true": num_true} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ComputeAccidentalHits", + Input: []tf.Input{ + true_classes, sampled_candidates, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2) +} + +// TensorArrayGatherV3Attr is an optional argument to TensorArrayGatherV3. +type TensorArrayGatherV3Attr func(optionalAttr) + +// TensorArrayGatherV3ElementShape sets the optional element_shape attribute to value. +// +// value: The expected shape of an element, if known. Used to +// validate the shapes of TensorArray elements. If this shape is not +// fully specified, gathering zero-size TensorArrays is an error. +// If not specified, defaults to +func TensorArrayGatherV3ElementShape(value tf.Shape) TensorArrayGatherV3Attr { + return func(m optionalAttr) { + m["element_shape"] = value + } +} + +// Gather specific elements from the TensorArray into output `value`. +// +// All elements selected by `indices` must have the same shape. +// +// Arguments: +// handle: The handle to a TensorArray. +// indices: The locations in the TensorArray from which to read tensor elements. +// flow_in: A float scalar that enforces proper chaining of operations. +// dtype: The type of the elem that is returned. +// +// Returns All of the elements in the TensorArray, concatenated along a new +// axis (the new dimension 0). +func TensorArrayGatherV3(scope *Scope, handle tf.Output, indices tf.Output, flow_in tf.Output, dtype tf.DataType, optional ...TensorArrayGatherV3Attr) (value tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"dtype": dtype} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "TensorArrayGatherV3", + Input: []tf.Input{ + handle, indices, flow_in, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Converts each string in the input Tensor to its hash mod by a number of buckets. +// +// The hash function is deterministic on the content of the string within the +// process and will never change. However, it is not suitable for cryptography. +// This function may be used when CPU time is scarce and inputs are trusted or +// unimportant. There is a risk of adversaries constructing inputs that all hash +// to the same bucket. To prevent this problem, use a strong hash function with +// `tf.string_to_hash_bucket_strong`. +// +// Arguments: +// input: The strings to assign a hash bucket. +// num_buckets: The number of buckets. // // Returns A Tensor of the same shape as the input `string_tensor`. func StringToHashBucketFast(scope *Scope, input tf.Output, num_buckets int64) (output tf.Output) { @@ -8454,6 +8627,81 @@ func RestoreV2(scope *Scope, prefix tf.Output, tensor_names tf.Output, shape_and return tensors } +// Computes the maximum along segments of a tensor. +// +// Read @{$math_ops#segmentation$the section on segmentation} for an explanation of +// segments. +// +// Computes a tensor such that +// \\(output_i = \max_j(data_j)\\) where `max` is over `j` such +// that `segment_ids[j] == i`. +// +// If the max is empty for a given segment ID `i`, `output[i] = 0`. +// +//
+// +//
+// +// Arguments: +// +// segment_ids: A 1-D tensor whose rank is equal to the rank of `data`'s +// first dimension. Values should be sorted and can be repeated. +// +// Returns Has same shape as data, except for dimension 0 which +// has size `k`, the number of segments. +func SegmentMax(scope *Scope, data tf.Output, segment_ids tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "SegmentMax", + Input: []tf.Input{ + data, segment_ids, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Creates a dataset that skips `count` elements from the `input_dataset`. +// +// Arguments: +// +// count: A scalar representing the number of elements from the `input_dataset` +// that should be skipped. If count is -1, skips everything. +// +// +func SkipDataset(scope *Scope, input_dataset tf.Output, count tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "SkipDataset", + Input: []tf.Input{ + input_dataset, count, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Computes hyperbolic tangent of `x` element-wise. +func Tanh(scope *Scope, x tf.Output) (y tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Tanh", + Input: []tf.Input{ + x, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Decode web-safe base64-encoded strings. // // Input may or may not have padding at the end. See EncodeBase64 for padding. @@ -8901,25 +9149,140 @@ func IRFFT2D(scope *Scope, input tf.Output, fft_length tf.Output) (output tf.Out return op.Output(0) } -// Transforms a vector of brain.Example protos (as strings) into typed tensors. +// DecodeJpegAttr is an optional argument to DecodeJpeg. +type DecodeJpegAttr func(optionalAttr) + +// DecodeJpegChannels sets the optional channels attribute to value. // -// Arguments: -// serialized: A vector containing a batch of binary serialized Example protos. -// names: A vector containing the names of the serialized protos. -// May contain, for example, table key (descriptive) names for the -// corresponding serialized protos. These are purely useful for debugging -// purposes, and the presence of values here has no effect on the output. -// May also be an empty vector if no names are available. -// If non-empty, this vector must be the same length as "serialized". -// sparse_keys: A list of Nsparse string Tensors (scalars). -// The keys expected in the Examples' features associated with sparse values. -// dense_keys: A list of Ndense string Tensors (scalars). -// The keys expected in the Examples' features associated with dense values. -// dense_defaults: A list of Ndense Tensors (some may be empty). -// dense_defaults[j] provides default values -// when the example's feature_map lacks dense_key[j]. If an empty Tensor is -// provided for dense_defaults[j], then the Feature dense_keys[j] is required. -// The input type is inferred from dense_defaults[j], even when it's empty. +// value: Number of color channels for the decoded image. +// If not specified, defaults to 0 +func DecodeJpegChannels(value int64) DecodeJpegAttr { + return func(m optionalAttr) { + m["channels"] = value + } +} + +// DecodeJpegRatio sets the optional ratio attribute to value. +// +// value: Downscaling ratio. +// If not specified, defaults to 1 +func DecodeJpegRatio(value int64) DecodeJpegAttr { + return func(m optionalAttr) { + m["ratio"] = value + } +} + +// DecodeJpegFancyUpscaling sets the optional fancy_upscaling attribute to value. +// +// value: If true use a slower but nicer upscaling of the +// chroma planes (yuv420/422 only). +// If not specified, defaults to true +func DecodeJpegFancyUpscaling(value bool) DecodeJpegAttr { + return func(m optionalAttr) { + m["fancy_upscaling"] = value + } +} + +// DecodeJpegTryRecoverTruncated sets the optional try_recover_truncated attribute to value. +// +// value: If true try to recover an image from truncated input. +// If not specified, defaults to false +func DecodeJpegTryRecoverTruncated(value bool) DecodeJpegAttr { + return func(m optionalAttr) { + m["try_recover_truncated"] = value + } +} + +// DecodeJpegAcceptableFraction sets the optional acceptable_fraction attribute to value. +// +// value: The minimum required fraction of lines before a truncated +// input is accepted. +// If not specified, defaults to 1 +func DecodeJpegAcceptableFraction(value float32) DecodeJpegAttr { + return func(m optionalAttr) { + m["acceptable_fraction"] = value + } +} + +// DecodeJpegDctMethod sets the optional dct_method attribute to value. +// +// value: string specifying a hint about the algorithm used for +// decompression. Defaults to "" which maps to a system-specific +// default. Currently valid values are ["INTEGER_FAST", +// "INTEGER_ACCURATE"]. The hint may be ignored (e.g., the internal +// jpeg library changes to a version that does not have that specific +// option.) +// If not specified, defaults to "" +func DecodeJpegDctMethod(value string) DecodeJpegAttr { + return func(m optionalAttr) { + m["dct_method"] = value + } +} + +// Decode a JPEG-encoded image to a uint8 tensor. +// +// The attr `channels` indicates the desired number of color channels for the +// decoded image. +// +// Accepted values are: +// +// * 0: Use the number of channels in the JPEG-encoded image. +// * 1: output a grayscale image. +// * 3: output an RGB image. +// +// If needed, the JPEG-encoded image is transformed to match the requested number +// of color channels. +// +// The attr `ratio` allows downscaling the image by an integer factor during +// decoding. Allowed values are: 1, 2, 4, and 8. This is much faster than +// downscaling the image later. +// +// +// This op also supports decoding PNGs and non-animated GIFs since the interface is +// the same, though it is cleaner to use `tf.image.decode_image`. +// +// Arguments: +// contents: 0-D. The JPEG-encoded image. +// +// Returns 3-D with shape `[height, width, channels]`.. +func DecodeJpeg(scope *Scope, contents tf.Output, optional ...DecodeJpegAttr) (image tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "DecodeJpeg", + Input: []tf.Input{ + contents, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Transforms a vector of brain.Example protos (as strings) into typed tensors. +// +// Arguments: +// serialized: A vector containing a batch of binary serialized Example protos. +// names: A vector containing the names of the serialized protos. +// May contain, for example, table key (descriptive) names for the +// corresponding serialized protos. These are purely useful for debugging +// purposes, and the presence of values here has no effect on the output. +// May also be an empty vector if no names are available. +// If non-empty, this vector must be the same length as "serialized". +// sparse_keys: A list of Nsparse string Tensors (scalars). +// The keys expected in the Examples' features associated with sparse values. +// dense_keys: A list of Ndense string Tensors (scalars). +// The keys expected in the Examples' features associated with dense values. +// dense_defaults: A list of Ndense Tensors (some may be empty). +// dense_defaults[j] provides default values +// when the example's feature_map lacks dense_key[j]. If an empty Tensor is +// provided for dense_defaults[j], then the Feature dense_keys[j] is required. +// The input type is inferred from dense_defaults[j], even when it's empty. // If dense_defaults[j] is not empty, and dense_shapes[j] is fully defined, // then the shape of dense_defaults[j] must match that of dense_shapes[j]. // If dense_shapes[j] has an undefined major dimension (variable strides dense @@ -9073,39 +9436,234 @@ func FFT(scope *Scope, input tf.Output) (output tf.Output) { input, }, } - op := scope.AddOperation(opspec) - return op.Output(0) + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// ResourceSparseApplyAdagradDAAttr is an optional argument to ResourceSparseApplyAdagradDA. +type ResourceSparseApplyAdagradDAAttr func(optionalAttr) + +// ResourceSparseApplyAdagradDAUseLocking sets the optional use_locking attribute to value. +// +// value: If True, updating of the var and accum tensors will be protected by +// a lock; otherwise the behavior is undefined, but may exhibit less contention. +// If not specified, defaults to false +func ResourceSparseApplyAdagradDAUseLocking(value bool) ResourceSparseApplyAdagradDAAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// Update entries in '*var' and '*accum' according to the proximal adagrad scheme. +// +// Arguments: +// var_: Should be from a Variable(). +// gradient_accumulator: Should be from a Variable(). +// gradient_squared_accumulator: Should be from a Variable(). +// grad: The gradient. +// indices: A vector of indices into the first dimension of var and accum. +// lr: Learning rate. Must be a scalar. +// l1: L1 regularization. Must be a scalar. +// l2: L2 regularization. Must be a scalar. +// global_step: Training step number. Must be a scalar. +// +// Returns the created operation. +func ResourceSparseApplyAdagradDA(scope *Scope, var_ tf.Output, gradient_accumulator tf.Output, gradient_squared_accumulator tf.Output, grad tf.Output, indices tf.Output, lr tf.Output, l1 tf.Output, l2 tf.Output, global_step tf.Output, optional ...ResourceSparseApplyAdagradDAAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceSparseApplyAdagradDA", + Input: []tf.Input{ + var_, gradient_accumulator, gradient_squared_accumulator, grad, indices, lr, l1, l2, global_step, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + +// EncodeJpegAttr is an optional argument to EncodeJpeg. +type EncodeJpegAttr func(optionalAttr) + +// EncodeJpegFormat sets the optional format attribute to value. +// +// value: Per pixel image format. +// If not specified, defaults to "" +func EncodeJpegFormat(value string) EncodeJpegAttr { + return func(m optionalAttr) { + m["format"] = value + } +} + +// EncodeJpegQuality sets the optional quality attribute to value. +// +// value: Quality of the compression from 0 to 100 (higher is better and slower). +// If not specified, defaults to 95 +func EncodeJpegQuality(value int64) EncodeJpegAttr { + return func(m optionalAttr) { + m["quality"] = value + } +} + +// EncodeJpegProgressive sets the optional progressive attribute to value. +// +// value: If True, create a JPEG that loads progressively (coarse to fine). +// If not specified, defaults to false +func EncodeJpegProgressive(value bool) EncodeJpegAttr { + return func(m optionalAttr) { + m["progressive"] = value + } +} + +// EncodeJpegOptimizeSize sets the optional optimize_size attribute to value. +// +// value: If True, spend CPU/RAM to reduce size with no quality change. +// If not specified, defaults to false +func EncodeJpegOptimizeSize(value bool) EncodeJpegAttr { + return func(m optionalAttr) { + m["optimize_size"] = value + } +} + +// EncodeJpegChromaDownsampling sets the optional chroma_downsampling attribute to value. +// +// value: See http://en.wikipedia.org/wiki/Chroma_subsampling. +// If not specified, defaults to true +func EncodeJpegChromaDownsampling(value bool) EncodeJpegAttr { + return func(m optionalAttr) { + m["chroma_downsampling"] = value + } +} + +// EncodeJpegDensityUnit sets the optional density_unit attribute to value. +// +// value: Unit used to specify `x_density` and `y_density`: +// pixels per inch (`'in'`) or centimeter (`'cm'`). +// If not specified, defaults to "in" +func EncodeJpegDensityUnit(value string) EncodeJpegAttr { + return func(m optionalAttr) { + m["density_unit"] = value + } +} + +// EncodeJpegXDensity sets the optional x_density attribute to value. +// +// value: Horizontal pixels per density unit. +// If not specified, defaults to 300 +func EncodeJpegXDensity(value int64) EncodeJpegAttr { + return func(m optionalAttr) { + m["x_density"] = value + } +} + +// EncodeJpegYDensity sets the optional y_density attribute to value. +// +// value: Vertical pixels per density unit. +// If not specified, defaults to 300 +func EncodeJpegYDensity(value int64) EncodeJpegAttr { + return func(m optionalAttr) { + m["y_density"] = value + } +} + +// EncodeJpegXmpMetadata sets the optional xmp_metadata attribute to value. +// +// value: If not empty, embed this XMP metadata in the image header. +// If not specified, defaults to "" +func EncodeJpegXmpMetadata(value string) EncodeJpegAttr { + return func(m optionalAttr) { + m["xmp_metadata"] = value + } +} + +// JPEG-encode an image. +// +// `image` is a 3-D uint8 Tensor of shape `[height, width, channels]`. +// +// The attr `format` can be used to override the color format of the encoded +// output. Values can be: +// +// * `''`: Use a default format based on the number of channels in the image. +// * `grayscale`: Output a grayscale JPEG image. The `channels` dimension +// of `image` must be 1. +// * `rgb`: Output an RGB JPEG image. The `channels` dimension +// of `image` must be 3. +// +// If `format` is not specified or is the empty string, a default format is picked +// in function of the number of channels in `image`: +// +// * 1: Output a grayscale image. +// * 3: Output an RGB image. +// +// Arguments: +// image: 3-D with shape `[height, width, channels]`. +// +// Returns 0-D. JPEG-encoded image. +func EncodeJpeg(scope *Scope, image tf.Output, optional ...EncodeJpegAttr) (contents tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "EncodeJpeg", + Input: []tf.Input{ + image, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// MultinomialAttr is an optional argument to Multinomial. +type MultinomialAttr func(optionalAttr) + +// MultinomialSeed sets the optional seed attribute to value. +// +// value: If either seed or seed2 is set to be non-zero, the internal random number +// generator is seeded by the given seed. Otherwise, a random seed is used. +// If not specified, defaults to 0 +func MultinomialSeed(value int64) MultinomialAttr { + return func(m optionalAttr) { + m["seed"] = value + } } -// ResourceSparseApplyAdagradDAAttr is an optional argument to ResourceSparseApplyAdagradDA. -type ResourceSparseApplyAdagradDAAttr func(optionalAttr) - -// ResourceSparseApplyAdagradDAUseLocking sets the optional use_locking attribute to value. +// MultinomialSeed2 sets the optional seed2 attribute to value. // -// value: If True, updating of the var and accum tensors will be protected by -// a lock; otherwise the behavior is undefined, but may exhibit less contention. -// If not specified, defaults to false -func ResourceSparseApplyAdagradDAUseLocking(value bool) ResourceSparseApplyAdagradDAAttr { +// value: A second seed to avoid seed collision. +// If not specified, defaults to 0 +func MultinomialSeed2(value int64) MultinomialAttr { return func(m optionalAttr) { - m["use_locking"] = value + m["seed2"] = value } } -// Update entries in '*var' and '*accum' according to the proximal adagrad scheme. +// MultinomialOutputDtype sets the optional output_dtype attribute to value. +// If not specified, defaults to DT_INT64 +func MultinomialOutputDtype(value tf.DataType) MultinomialAttr { + return func(m optionalAttr) { + m["output_dtype"] = value + } +} + +// Draws samples from a multinomial distribution. // // Arguments: -// var_: Should be from a Variable(). -// gradient_accumulator: Should be from a Variable(). -// gradient_squared_accumulator: Should be from a Variable(). -// grad: The gradient. -// indices: A vector of indices into the first dimension of var and accum. -// lr: Learning rate. Must be a scalar. -// l1: L1 regularization. Must be a scalar. -// l2: L2 regularization. Must be a scalar. -// global_step: Training step number. Must be a scalar. +// logits: 2-D Tensor with shape `[batch_size, num_classes]`. Each slice `[i, :]` +// represents the unnormalized log probabilities for all classes. +// num_samples: 0-D. Number of independent samples to draw for each row slice. // -// Returns the created operation. -func ResourceSparseApplyAdagradDA(scope *Scope, var_ tf.Output, gradient_accumulator tf.Output, gradient_squared_accumulator tf.Output, grad tf.Output, indices tf.Output, lr tf.Output, l1 tf.Output, l2 tf.Output, global_step tf.Output, optional ...ResourceSparseApplyAdagradDAAttr) (o *tf.Operation) { +// Returns 2-D Tensor with shape `[batch_size, num_samples]`. Each slice `[i, :]` +// contains the drawn class labels with range `[0, num_classes)`. +func Multinomial(scope *Scope, logits tf.Output, num_samples tf.Output, optional ...MultinomialAttr) (output tf.Output) { if scope.Err() != nil { return } @@ -9114,13 +9672,14 @@ func ResourceSparseApplyAdagradDA(scope *Scope, var_ tf.Output, gradient_accumul a(attrs) } opspec := tf.OpSpec{ - Type: "ResourceSparseApplyAdagradDA", + Type: "Multinomial", Input: []tf.Input{ - var_, gradient_accumulator, gradient_squared_accumulator, grad, indices, lr, l1, l2, global_step, + logits, num_samples, }, Attrs: attrs, } - return scope.AddOperation(opspec) + op := scope.AddOperation(opspec) + return op.Output(0) } // Returns the truth value of NOT x element-wise. @@ -14914,208 +15473,12 @@ func SparseMatMulBIsSparse(value bool) SparseMatMulAttr { // // The inputs must be two-dimensional matrices and the inner dimension of "a" must // match the outer dimension of "b". This op is optimized for the case where at -// least one of "a" or "b" is sparse. The breakeven for using this versus a dense -// matrix multiply on one platform was 30% zero values in the sparse matrix. -// -// The gradient computation of this operation will only take advantage of sparsity -// in the input gradient when that gradient comes from a Relu. -func SparseMatMul(scope *Scope, a tf.Output, b tf.Output, optional ...SparseMatMulAttr) (product tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "SparseMatMul", - Input: []tf.Input{ - a, b, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// MultinomialAttr is an optional argument to Multinomial. -type MultinomialAttr func(optionalAttr) - -// MultinomialSeed sets the optional seed attribute to value. -// -// value: If either seed or seed2 is set to be non-zero, the internal random number -// generator is seeded by the given seed. Otherwise, a random seed is used. -// If not specified, defaults to 0 -func MultinomialSeed(value int64) MultinomialAttr { - return func(m optionalAttr) { - m["seed"] = value - } -} - -// MultinomialSeed2 sets the optional seed2 attribute to value. -// -// value: A second seed to avoid seed collision. -// If not specified, defaults to 0 -func MultinomialSeed2(value int64) MultinomialAttr { - return func(m optionalAttr) { - m["seed2"] = value - } -} - -// MultinomialOutputDtype sets the optional output_dtype attribute to value. -// If not specified, defaults to DT_INT64 -func MultinomialOutputDtype(value tf.DataType) MultinomialAttr { - return func(m optionalAttr) { - m["output_dtype"] = value - } -} - -// Draws samples from a multinomial distribution. -// -// Arguments: -// logits: 2-D Tensor with shape `[batch_size, num_classes]`. Each slice `[i, :]` -// represents the unnormalized log probabilities for all classes. -// num_samples: 0-D. Number of independent samples to draw for each row slice. -// -// Returns 2-D Tensor with shape `[batch_size, num_samples]`. Each slice `[i, :]` -// contains the drawn class labels with range `[0, num_classes)`. -func Multinomial(scope *Scope, logits tf.Output, num_samples tf.Output, optional ...MultinomialAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "Multinomial", - Input: []tf.Input{ - logits, num_samples, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// EncodeJpegAttr is an optional argument to EncodeJpeg. -type EncodeJpegAttr func(optionalAttr) - -// EncodeJpegFormat sets the optional format attribute to value. -// -// value: Per pixel image format. -// If not specified, defaults to "" -func EncodeJpegFormat(value string) EncodeJpegAttr { - return func(m optionalAttr) { - m["format"] = value - } -} - -// EncodeJpegQuality sets the optional quality attribute to value. -// -// value: Quality of the compression from 0 to 100 (higher is better and slower). -// If not specified, defaults to 95 -func EncodeJpegQuality(value int64) EncodeJpegAttr { - return func(m optionalAttr) { - m["quality"] = value - } -} - -// EncodeJpegProgressive sets the optional progressive attribute to value. -// -// value: If True, create a JPEG that loads progressively (coarse to fine). -// If not specified, defaults to false -func EncodeJpegProgressive(value bool) EncodeJpegAttr { - return func(m optionalAttr) { - m["progressive"] = value - } -} - -// EncodeJpegOptimizeSize sets the optional optimize_size attribute to value. -// -// value: If True, spend CPU/RAM to reduce size with no quality change. -// If not specified, defaults to false -func EncodeJpegOptimizeSize(value bool) EncodeJpegAttr { - return func(m optionalAttr) { - m["optimize_size"] = value - } -} - -// EncodeJpegChromaDownsampling sets the optional chroma_downsampling attribute to value. -// -// value: See http://en.wikipedia.org/wiki/Chroma_subsampling. -// If not specified, defaults to true -func EncodeJpegChromaDownsampling(value bool) EncodeJpegAttr { - return func(m optionalAttr) { - m["chroma_downsampling"] = value - } -} - -// EncodeJpegDensityUnit sets the optional density_unit attribute to value. -// -// value: Unit used to specify `x_density` and `y_density`: -// pixels per inch (`'in'`) or centimeter (`'cm'`). -// If not specified, defaults to "in" -func EncodeJpegDensityUnit(value string) EncodeJpegAttr { - return func(m optionalAttr) { - m["density_unit"] = value - } -} - -// EncodeJpegXDensity sets the optional x_density attribute to value. -// -// value: Horizontal pixels per density unit. -// If not specified, defaults to 300 -func EncodeJpegXDensity(value int64) EncodeJpegAttr { - return func(m optionalAttr) { - m["x_density"] = value - } -} - -// EncodeJpegYDensity sets the optional y_density attribute to value. -// -// value: Vertical pixels per density unit. -// If not specified, defaults to 300 -func EncodeJpegYDensity(value int64) EncodeJpegAttr { - return func(m optionalAttr) { - m["y_density"] = value - } -} - -// EncodeJpegXmpMetadata sets the optional xmp_metadata attribute to value. -// -// value: If not empty, embed this XMP metadata in the image header. -// If not specified, defaults to "" -func EncodeJpegXmpMetadata(value string) EncodeJpegAttr { - return func(m optionalAttr) { - m["xmp_metadata"] = value - } -} - -// JPEG-encode an image. -// -// `image` is a 3-D uint8 Tensor of shape `[height, width, channels]`. -// -// The attr `format` can be used to override the color format of the encoded -// output. Values can be: -// -// * `''`: Use a default format based on the number of channels in the image. -// * `grayscale`: Output a grayscale JPEG image. The `channels` dimension -// of `image` must be 1. -// * `rgb`: Output an RGB JPEG image. The `channels` dimension -// of `image` must be 3. -// -// If `format` is not specified or is the empty string, a default format is picked -// in function of the number of channels in `image`: -// -// * 1: Output a grayscale image. -// * 3: Output an RGB image. -// -// Arguments: -// image: 3-D with shape `[height, width, channels]`. +// least one of "a" or "b" is sparse. The breakeven for using this versus a dense +// matrix multiply on one platform was 30% zero values in the sparse matrix. // -// Returns 0-D. JPEG-encoded image. -func EncodeJpeg(scope *Scope, image tf.Output, optional ...EncodeJpegAttr) (contents tf.Output) { +// The gradient computation of this operation will only take advantage of sparsity +// in the input gradient when that gradient comes from a Relu. +func SparseMatMul(scope *Scope, a tf.Output, b tf.Output, optional ...SparseMatMulAttr) (product tf.Output) { if scope.Err() != nil { return } @@ -15124,9 +15487,9 @@ func EncodeJpeg(scope *Scope, image tf.Output, optional ...EncodeJpegAttr) (cont a(attrs) } opspec := tf.OpSpec{ - Type: "EncodeJpeg", + Type: "SparseMatMul", Input: []tf.Input{ - image, + a, b, }, Attrs: attrs, } @@ -15513,6 +15876,45 @@ func ResourceScatterAdd(scope *Scope, resource tf.Output, indices tf.Output, upd return scope.AddOperation(opspec) } +// Says whether the targets are in the top `K` predictions. +// +// This outputs a `batch_size` bool array, an entry `out[i]` is `true` if the +// prediction for the target class is among the top `k` predictions among +// all predictions for example `i`. Note that the behavior of `InTopK` differs +// from the `TopK` op in its handling of ties; if multiple classes have the +// same prediction value and straddle the top-`k` boundary, all of those +// classes are considered to be in the top `k`. +// +// More formally, let +// +// \\(predictions_i\\) be the predictions for all classes for example `i`, +// \\(targets_i\\) be the target class for example `i`, +// \\(out_i\\) be the output for example `i`, +// +// $$out_i = predictions_{i, targets_i} \in TopKIncludingTies(predictions_i)$$ +// +// Arguments: +// predictions: A `batch_size` x `classes` tensor. +// targets: A `batch_size` vector of class ids. +// k: Number of top elements to look at for computing precision. +// +// Returns Computed Precision at `k` as a `bool Tensor`. +func InTopK(scope *Scope, predictions tf.Output, targets tf.Output, k int64) (precision tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"k": k} + opspec := tf.OpSpec{ + Type: "InTopK", + Input: []tf.Input{ + predictions, targets, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Computes the gradient for the inverse of `x` wrt its input. // // Specifically, `grad = -dy * y*y`, where `y = 1/x`, and `dy` @@ -16588,243 +16990,75 @@ func NextIteration(scope *Scope, data tf.Output) (output tf.Output) { return op.Output(0) } -// Creates a dataset that skips `count` elements from the `input_dataset`. -// -// Arguments: -// -// count: A scalar representing the number of elements from the `input_dataset` -// that should be skipped. If count is -1, skips everything. -// -// -func SkipDataset(scope *Scope, input_dataset tf.Output, count tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} - opspec := tf.OpSpec{ - Type: "SkipDataset", - Input: []tf.Input{ - input_dataset, count, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Computes hyperbolic tangent of `x` element-wise. -func Tanh(scope *Scope, x tf.Output) (y tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Tanh", - Input: []tf.Input{ - x, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Computes the maximum along segments of a tensor. -// -// Read @{$math_ops#segmentation$the section on segmentation} for an explanation of -// segments. -// -// Computes a tensor such that -// \\(output_i = \max_j(data_j)\\) where `max` is over `j` such -// that `segment_ids[j] == i`. -// -// If the max is empty for a given segment ID `i`, `output[i] = 0`. -// -//
-// -//
-// -// Arguments: -// -// segment_ids: A 1-D tensor whose rank is equal to the rank of `data`'s -// first dimension. Values should be sorted and can be repeated. -// -// Returns Has same shape as data, except for dimension 0 which -// has size `k`, the number of segments. -func SegmentMax(scope *Scope, data tf.Output, segment_ids tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "SegmentMax", - Input: []tf.Input{ - data, segment_ids, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// AvgPoolGradAttr is an optional argument to AvgPoolGrad. -type AvgPoolGradAttr func(optionalAttr) - -// AvgPoolGradDataFormat sets the optional data_format attribute to value. -// -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the data is stored in the order of: -// [batch, in_height, in_width, in_channels]. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, in_channels, in_height, in_width]. -// If not specified, defaults to "NHWC" -func AvgPoolGradDataFormat(value string) AvgPoolGradAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// Computes gradients of the average pooling function. -// -// Arguments: -// orig_input_shape: 1-D. Shape of the original input to `avg_pool`. -// grad: 4-D with shape `[batch, height, width, channels]`. Gradients w.r.t. -// the output of `avg_pool`. -// ksize: The size of the sliding window for each dimension of the input. -// strides: The stride of the sliding window for each dimension of the input. -// padding: The type of padding algorithm to use. -// -// Returns 4-D. Gradients w.r.t. the input of `avg_pool`. -func AvgPoolGrad(scope *Scope, orig_input_shape tf.Output, grad tf.Output, ksize []int64, strides []int64, padding string, optional ...AvgPoolGradAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "AvgPoolGrad", - Input: []tf.Input{ - orig_input_shape, grad, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// StageClearAttr is an optional argument to StageClear. -type StageClearAttr func(optionalAttr) +// MapPeekAttr is an optional argument to MapPeek. +type MapPeekAttr func(optionalAttr) -// StageClearCapacity sets the optional capacity attribute to value. +// MapPeekCapacity sets the optional capacity attribute to value. // If not specified, defaults to 0 // // REQUIRES: value >= 0 -func StageClearCapacity(value int64) StageClearAttr { +func MapPeekCapacity(value int64) MapPeekAttr { return func(m optionalAttr) { m["capacity"] = value } } -// StageClearMemoryLimit sets the optional memory_limit attribute to value. +// MapPeekMemoryLimit sets the optional memory_limit attribute to value. // If not specified, defaults to 0 // // REQUIRES: value >= 0 -func StageClearMemoryLimit(value int64) StageClearAttr { +func MapPeekMemoryLimit(value int64) MapPeekAttr { return func(m optionalAttr) { m["memory_limit"] = value } } -// StageClearContainer sets the optional container attribute to value. +// MapPeekContainer sets the optional container attribute to value. // If not specified, defaults to "" -func StageClearContainer(value string) StageClearAttr { +func MapPeekContainer(value string) MapPeekAttr { return func(m optionalAttr) { m["container"] = value } } -// StageClearSharedName sets the optional shared_name attribute to value. +// MapPeekSharedName sets the optional shared_name attribute to value. // If not specified, defaults to "" -func StageClearSharedName(value string) StageClearAttr { +func MapPeekSharedName(value string) MapPeekAttr { return func(m optionalAttr) { m["shared_name"] = value } } -// Op removes all elements in the underlying container. -// -// Returns the created operation. -func StageClear(scope *Scope, dtypes []tf.DataType, optional ...StageClearAttr) (o *tf.Operation) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"dtypes": dtypes} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "StageClear", - - Attrs: attrs, - } - return scope.AddOperation(opspec) -} - -// ComputeAccidentalHitsAttr is an optional argument to ComputeAccidentalHits. -type ComputeAccidentalHitsAttr func(optionalAttr) - -// ComputeAccidentalHitsSeed sets the optional seed attribute to value. -// -// value: If either seed or seed2 are set to be non-zero, the random number -// generator is seeded by the given seed. Otherwise, it is seeded by a -// random seed. -// If not specified, defaults to 0 -func ComputeAccidentalHitsSeed(value int64) ComputeAccidentalHitsAttr { - return func(m optionalAttr) { - m["seed"] = value - } -} - -// ComputeAccidentalHitsSeed2 sets the optional seed2 attribute to value. -// -// value: An second seed to avoid seed collision. -// If not specified, defaults to 0 -func ComputeAccidentalHitsSeed2(value int64) ComputeAccidentalHitsAttr { - return func(m optionalAttr) { - m["seed2"] = value - } -} - -// Computes the ids of the positions in sampled_candidates that match true_labels. -// -// When doing log-odds NCE, the result of this op should be passed through a -// SparseToDense op, then added to the logits of the sampled candidates. This has -// the effect of 'removing' the sampled labels that match the true labels by -// making the classifier sure that they are sampled labels. -// -// Arguments: -// true_classes: The true_classes output of UnpackSparseLabels. -// sampled_candidates: The sampled_candidates output of CandidateSampler. -// num_true: Number of true labels per context. +// Op peeks at the values at the specified key. If the // -// Returns A vector of indices corresponding to rows of true_candidates.A vector of IDs of positions in sampled_candidates that match a true_label -// for the row with the corresponding index in indices.A vector of the same length as indices and ids, in which each element -// is -FLOAT_MAX. -func ComputeAccidentalHits(scope *Scope, true_classes tf.Output, sampled_candidates tf.Output, num_true int64, optional ...ComputeAccidentalHitsAttr) (indices tf.Output, ids tf.Output, weights tf.Output) { +// underlying container does not contain this key +// this op will block until it does. +func MapPeek(scope *Scope, key tf.Output, indices tf.Output, dtypes []tf.DataType, optional ...MapPeekAttr) (values []tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"num_true": num_true} + attrs := map[string]interface{}{"dtypes": dtypes} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "ComputeAccidentalHits", + Type: "MapPeek", Input: []tf.Input{ - true_classes, sampled_candidates, + key, indices, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) + if scope.Err() != nil { + return + } + var idx int + var err error + if values, idx, err = makeOutputList(op, idx, "values"); err != nil { + scope.UpdateErr("MapPeek", err) + return + } + return values } // Looks up keys in a table, outputs the corresponding values. @@ -18790,29 +19024,272 @@ func DenseToDenseSetOperation(scope *Scope, set1 tf.Output, set2 tf.Output, set_ opspec := tf.OpSpec{ Type: "DenseToDenseSetOperation", Input: []tf.Input{ - set1, set2, + set1, set2, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2) +} + +// Generate a sharded filename. The filename is printf formatted as +// +// %s-%05d-of-%05d, basename, shard, num_shards. +func ShardedFilename(scope *Scope, basename tf.Output, shard tf.Output, num_shards tf.Output) (filename tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ShardedFilename", + Input: []tf.Input{ + basename, shard, num_shards, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// BatchToSpace for N-D tensors of type T. +// +// This operation reshapes the "batch" dimension 0 into `M + 1` dimensions of shape +// `block_shape + [batch]`, interleaves these blocks back into the grid defined by +// the spatial dimensions `[1, ..., M]`, to obtain a result with the same rank as +// the input. The spatial dimensions of this intermediate result are then +// optionally cropped according to `crops` to produce the output. This is the +// reverse of SpaceToBatch. See below for a precise description. +// +// Arguments: +// input: N-D with shape `input_shape = [batch] + spatial_shape + remaining_shape`, +// where spatial_shape has M dimensions. +// block_shape: 1-D with shape `[M]`, all values must be >= 1. +// crops: 2-D with shape `[M, 2]`, all values must be >= 0. +// `crops[i] = [crop_start, crop_end]` specifies the amount to crop from input +// dimension `i + 1`, which corresponds to spatial dimension `i`. It is +// required that +// `crop_start[i] + crop_end[i] <= block_shape[i] * input_shape[i + 1]`. +// +// This operation is equivalent to the following steps: +// +// 1. Reshape `input` to `reshaped` of shape: +// [block_shape[0], ..., block_shape[M-1], +// batch / prod(block_shape), +// input_shape[1], ..., input_shape[N-1]] +// +// 2. Permute dimensions of `reshaped` to produce `permuted` of shape +// [batch / prod(block_shape), +// +// input_shape[1], block_shape[0], +// ..., +// input_shape[M], block_shape[M-1], +// +// input_shape[M+1], ..., input_shape[N-1]] +// +// 3. Reshape `permuted` to produce `reshaped_permuted` of shape +// [batch / prod(block_shape), +// +// input_shape[1] * block_shape[0], +// ..., +// input_shape[M] * block_shape[M-1], +// +// input_shape[M+1], +// ..., +// input_shape[N-1]] +// +// 4. Crop the start and end of dimensions `[1, ..., M]` of +// `reshaped_permuted` according to `crops` to produce the output of shape: +// [batch / prod(block_shape), +// +// input_shape[1] * block_shape[0] - crops[0,0] - crops[0,1], +// ..., +// input_shape[M] * block_shape[M-1] - crops[M-1,0] - crops[M-1,1], +// +// input_shape[M+1], ..., input_shape[N-1]] +// +// Some examples: +// +// (1) For the following input of shape `[4, 1, 1, 1]`, `block_shape = [2, 2]`, and +// `crops = [[0, 0], [0, 0]]`: +// +// ``` +// [[[[1]]], [[[2]]], [[[3]]], [[[4]]]] +// ``` +// +// The output tensor has shape `[1, 2, 2, 1]` and value: +// +// ``` +// x = [[[[1], [2]], [[3], [4]]]] +// ``` +// +// (2) For the following input of shape `[4, 1, 1, 3]`, `block_shape = [2, 2]`, and +// `crops = [[0, 0], [0, 0]]`: +// +// ``` +// [[[1, 2, 3]], [[4, 5, 6]], [[7, 8, 9]], [[10, 11, 12]]] +// ``` +// +// The output tensor has shape `[1, 2, 2, 3]` and value: +// +// ``` +// x = [[[[1, 2, 3], [4, 5, 6]], +// [[7, 8, 9], [10, 11, 12]]]] +// ``` +// +// (3) For the following input of shape `[4, 2, 2, 1]`, `block_shape = [2, 2]`, and +// `crops = [[0, 0], [0, 0]]`: +// +// ``` +// x = [[[[1], [3]], [[9], [11]]], +// [[[2], [4]], [[10], [12]]], +// [[[5], [7]], [[13], [15]]], +// [[[6], [8]], [[14], [16]]]] +// ``` +// +// The output tensor has shape `[1, 4, 4, 1]` and value: +// +// ``` +// x = [[[1], [2], [3], [4]], +// [[5], [6], [7], [8]], +// [[9], [10], [11], [12]], +// [[13], [14], [15], [16]]] +// ``` +// +// (4) For the following input of shape `[8, 1, 3, 1]`, `block_shape = [2, 2]`, and +// `crops = [[0, 0], [2, 0]]`: +// +// ``` +// x = [[[[0], [1], [3]]], [[[0], [9], [11]]], +// [[[0], [2], [4]]], [[[0], [10], [12]]], +// [[[0], [5], [7]]], [[[0], [13], [15]]], +// [[[0], [6], [8]]], [[[0], [14], [16]]]] +// ``` +// +// The output tensor has shape `[2, 2, 4, 1]` and value: +// +// ``` +// x = [[[[1], [2], [3], [4]], +// [[5], [6], [7], [8]]], +// [[[9], [10], [11], [12]], +// [[13], [14], [15], [16]]]] +// ``` +func BatchToSpaceND(scope *Scope, input tf.Output, block_shape tf.Output, crops tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "BatchToSpaceND", + Input: []tf.Input{ + input, block_shape, crops, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// UnpackAttr is an optional argument to Unpack. +type UnpackAttr func(optionalAttr) + +// UnpackAxis sets the optional axis attribute to value. +// +// value: Dimension along which to unpack. Negative values wrap around, so the +// valid range is `[-R, R)`. +// If not specified, defaults to 0 +func UnpackAxis(value int64) UnpackAttr { + return func(m optionalAttr) { + m["axis"] = value + } +} + +// Unpacks a given dimension of a rank-`R` tensor into `num` rank-`(R-1)` tensors. +// +// Unpacks `num` tensors from `value` by chipping it along the `axis` dimension. +// For example, given a tensor of shape `(A, B, C, D)`; +// +// If `axis == 0` then the i'th tensor in `output` is the slice `value[i, :, :, :]` +// and each tensor in `output` will have shape `(B, C, D)`. (Note that the +// dimension unpacked along is gone, unlike `split`). +// +// If `axis == 1` then the i'th tensor in `output` is the slice `value[:, i, :, :]` +// and each tensor in `output` will have shape `(A, C, D)`. +// Etc. +// +// This is the opposite of `pack`. +// +// Arguments: +// value: 1-D or higher, with `axis` dimension size equal to `num`. +// +// +// Returns The list of tensors unpacked from `value`. +func Unpack(scope *Scope, value tf.Output, num int64, optional ...UnpackAttr) (output []tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"num": num} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "Unpack", + Input: []tf.Input{ + value, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + if scope.Err() != nil { + return + } + var idx int + var err error + if output, idx, err = makeOutputList(op, idx, "output"); err != nil { + scope.UpdateErr("Unpack", err) + return + } + return output +} + +// Increments variable pointed to by 'resource' until it reaches 'limit'. +// +// Arguments: +// resource: Should be from a scalar `Variable` node. +// limit: If incrementing ref would bring it above limit, instead generates an +// 'OutOfRange' error. +// +// +// Returns A copy of the input before increment. If nothing else modifies the +// input, the values produced will all be distinct. +func ResourceCountUpTo(scope *Scope, resource tf.Output, limit int64, T tf.DataType) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"limit": limit, "T": T} + opspec := tf.OpSpec{ + Type: "ResourceCountUpTo", + Input: []tf.Input{ + resource, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) + return op.Output(0) } -// Generate a sharded filename. The filename is printf formatted as +// Delete the stack from its resource container. // -// %s-%05d-of-%05d, basename, shard, num_shards. -func ShardedFilename(scope *Scope, basename tf.Output, shard tf.Output, num_shards tf.Output) (filename tf.Output) { +// Arguments: +// handle: The handle to a stack. +// +// Returns the created operation. +func StackCloseV2(scope *Scope, handle tf.Output) (o *tf.Operation) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "ShardedFilename", + Type: "StackCloseV2", Input: []tf.Input{ - basename, shard, num_shards, + handle, }, } - op := scope.AddOperation(opspec) - return op.Output(0) + return scope.AddOperation(opspec) } // Generate a glob pattern matching all sharded file names. @@ -19443,121 +19920,6 @@ func ResizeNearestNeighborGrad(scope *Scope, grads tf.Output, size tf.Output, op return op.Output(0) } -// DecodeJpegAttr is an optional argument to DecodeJpeg. -type DecodeJpegAttr func(optionalAttr) - -// DecodeJpegChannels sets the optional channels attribute to value. -// -// value: Number of color channels for the decoded image. -// If not specified, defaults to 0 -func DecodeJpegChannels(value int64) DecodeJpegAttr { - return func(m optionalAttr) { - m["channels"] = value - } -} - -// DecodeJpegRatio sets the optional ratio attribute to value. -// -// value: Downscaling ratio. -// If not specified, defaults to 1 -func DecodeJpegRatio(value int64) DecodeJpegAttr { - return func(m optionalAttr) { - m["ratio"] = value - } -} - -// DecodeJpegFancyUpscaling sets the optional fancy_upscaling attribute to value. -// -// value: If true use a slower but nicer upscaling of the -// chroma planes (yuv420/422 only). -// If not specified, defaults to true -func DecodeJpegFancyUpscaling(value bool) DecodeJpegAttr { - return func(m optionalAttr) { - m["fancy_upscaling"] = value - } -} - -// DecodeJpegTryRecoverTruncated sets the optional try_recover_truncated attribute to value. -// -// value: If true try to recover an image from truncated input. -// If not specified, defaults to false -func DecodeJpegTryRecoverTruncated(value bool) DecodeJpegAttr { - return func(m optionalAttr) { - m["try_recover_truncated"] = value - } -} - -// DecodeJpegAcceptableFraction sets the optional acceptable_fraction attribute to value. -// -// value: The minimum required fraction of lines before a truncated -// input is accepted. -// If not specified, defaults to 1 -func DecodeJpegAcceptableFraction(value float32) DecodeJpegAttr { - return func(m optionalAttr) { - m["acceptable_fraction"] = value - } -} - -// DecodeJpegDctMethod sets the optional dct_method attribute to value. -// -// value: string specifying a hint about the algorithm used for -// decompression. Defaults to "" which maps to a system-specific -// default. Currently valid values are ["INTEGER_FAST", -// "INTEGER_ACCURATE"]. The hint may be ignored (e.g., the internal -// jpeg library changes to a version that does not have that specific -// option.) -// If not specified, defaults to "" -func DecodeJpegDctMethod(value string) DecodeJpegAttr { - return func(m optionalAttr) { - m["dct_method"] = value - } -} - -// Decode a JPEG-encoded image to a uint8 tensor. -// -// The attr `channels` indicates the desired number of color channels for the -// decoded image. -// -// Accepted values are: -// -// * 0: Use the number of channels in the JPEG-encoded image. -// * 1: output a grayscale image. -// * 3: output an RGB image. -// -// If needed, the JPEG-encoded image is transformed to match the requested number -// of color channels. -// -// The attr `ratio` allows downscaling the image by an integer factor during -// decoding. Allowed values are: 1, 2, 4, and 8. This is much faster than -// downscaling the image later. -// -// -// This op also supports decoding PNGs and non-animated GIFs since the interface is -// the same, though it is cleaner to use `tf.image.decode_image`. -// -// Arguments: -// contents: 0-D. The JPEG-encoded image. -// -// Returns 3-D with shape `[height, width, channels]`.. -func DecodeJpeg(scope *Scope, contents tf.Output, optional ...DecodeJpegAttr) (image tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "DecodeJpeg", - Input: []tf.Input{ - contents, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // ExtractJpegShapeAttr is an optional argument to ExtractJpegShape. type ExtractJpegShapeAttr func(optionalAttr) @@ -25121,139 +25483,20 @@ func Exit(scope *Scope, data tf.Output) (output tf.Output) { data, }, } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Returns a copy of the input tensor. -func Snapshot(scope *Scope, input tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Snapshot", - Input: []tf.Input{ - input, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// SpaceToDepthAttr is an optional argument to SpaceToDepth. -type SpaceToDepthAttr func(optionalAttr) - -// SpaceToDepthDataFormat sets the optional data_format attribute to value. -// If not specified, defaults to "NHWC" -func SpaceToDepthDataFormat(value string) SpaceToDepthAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// SpaceToDepth for tensors of type T. -// -// Rearranges blocks of spatial data, into depth. More specifically, -// this op outputs a copy of the input tensor where values from the `height` -// and `width` dimensions are moved to the `depth` dimension. -// The attr `block_size` indicates the input block size. -// -// * Non-overlapping blocks of size `block_size x block size` are rearranged -// into depth at each location. -// * The depth of the output tensor is `block_size * block_size * input_depth`. -// * The Y, X coordinates within each block of the input become the high order -// component of the output channel index. -// * The input tensor's height and width must be divisible by block_size. -// -// The `data_format` attr specifies the layout of the input and output tensors -// with the following options: -// "NHWC": `[ batch, height, width, channels ]` -// "NCHW": `[ batch, channels, height, width ]` -// "NCHW_VECT_C": -// `qint8 [ batch, channels / 4, height, width, 4 ]` -// -// It is useful to consider the operation as transforming a 6-D Tensor. -// e.g. for data_format = NHWC, -// Each element in the input tensor can be specified via 6 coordinates, -// ordered by decreasing memory layout significance as: -// n,oY,bY,oX,bX,iC (where n=batch index, oX, oY means X or Y coordinates -// within the output image, bX, bY means coordinates -// within the input block, iC means input channels). -// The output would be a transpose to the following layout: -// n,oY,oX,bY,bX,iC -// -// This operation is useful for resizing the activations between convolutions -// (but keeping all data), e.g. instead of pooling. It is also useful for training -// purely convolutional models. -// -// For example, given an input of shape `[1, 2, 2, 1]`, data_format = "NHWC" and -// block_size = 2: -// -// ``` -// x = [[[[1], [2]], -// [[3], [4]]]] -// ``` -// -// This operation will output a tensor of shape `[1, 1, 1, 4]`: -// -// ``` -// [[[[1, 2, 3, 4]]]] -// ``` -// -// Here, the input has a batch of 1 and each batch element has shape `[2, 2, 1]`, -// the corresponding output will have a single element (i.e. width and height are -// both 1) and will have a depth of 4 channels (1 * block_size * block_size). -// The output element shape is `[1, 1, 4]`. -// -// For an input tensor with larger depth, here of shape `[1, 2, 2, 3]`, e.g. -// -// ``` -// x = [[[[1, 2, 3], [4, 5, 6]], -// [[7, 8, 9], [10, 11, 12]]]] -// ``` -// -// This operation, for block_size of 2, will return the following tensor of shape -// `[1, 1, 1, 12]` -// -// ``` -// [[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]]] -// ``` -// -// Similarly, for the following input of shape `[1 4 4 1]`, and a block size of 2: -// -// ``` -// x = [[[[1], [2], [5], [6]], -// [[3], [4], [7], [8]], -// [[9], [10], [13], [14]], -// [[11], [12], [15], [16]]]] -// ``` -// -// the operator will return the following tensor of shape `[1 2 2 4]`: -// -// ``` -// x = [[[[1, 2, 3, 4], -// [5, 6, 7, 8]], -// [[9, 10, 11, 12], -// [13, 14, 15, 16]]]] -// ``` -// -// Arguments: -// -// block_size: The size of the spatial block. -func SpaceToDepth(scope *Scope, input tf.Output, block_size int64, optional ...SpaceToDepthAttr) (output tf.Output) { + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Returns a copy of the input tensor. +func Snapshot(scope *Scope, input tf.Output) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"block_size": block_size} - for _, a := range optional { - a(attrs) - } opspec := tf.OpSpec{ - Type: "SpaceToDepth", + Type: "Snapshot", Input: []tf.Input{ input, }, - Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) @@ -27644,246 +27887,3 @@ func SpaceToBatch(scope *Scope, input tf.Output, paddings tf.Output, block_size op := scope.AddOperation(opspec) return op.Output(0) } - -// UnpackAttr is an optional argument to Unpack. -type UnpackAttr func(optionalAttr) - -// UnpackAxis sets the optional axis attribute to value. -// -// value: Dimension along which to unpack. Negative values wrap around, so the -// valid range is `[-R, R)`. -// If not specified, defaults to 0 -func UnpackAxis(value int64) UnpackAttr { - return func(m optionalAttr) { - m["axis"] = value - } -} - -// Unpacks a given dimension of a rank-`R` tensor into `num` rank-`(R-1)` tensors. -// -// Unpacks `num` tensors from `value` by chipping it along the `axis` dimension. -// For example, given a tensor of shape `(A, B, C, D)`; -// -// If `axis == 0` then the i'th tensor in `output` is the slice `value[i, :, :, :]` -// and each tensor in `output` will have shape `(B, C, D)`. (Note that the -// dimension unpacked along is gone, unlike `split`). -// -// If `axis == 1` then the i'th tensor in `output` is the slice `value[:, i, :, :]` -// and each tensor in `output` will have shape `(A, C, D)`. -// Etc. -// -// This is the opposite of `pack`. -// -// Arguments: -// value: 1-D or higher, with `axis` dimension size equal to `num`. -// -// -// Returns The list of tensors unpacked from `value`. -func Unpack(scope *Scope, value tf.Output, num int64, optional ...UnpackAttr) (output []tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"num": num} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "Unpack", - Input: []tf.Input{ - value, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - if scope.Err() != nil { - return - } - var idx int - var err error - if output, idx, err = makeOutputList(op, idx, "output"); err != nil { - scope.UpdateErr("Unpack", err) - return - } - return output -} - -// Increments variable pointed to by 'resource' until it reaches 'limit'. -// -// Arguments: -// resource: Should be from a scalar `Variable` node. -// limit: If incrementing ref would bring it above limit, instead generates an -// 'OutOfRange' error. -// -// -// Returns A copy of the input before increment. If nothing else modifies the -// input, the values produced will all be distinct. -func ResourceCountUpTo(scope *Scope, resource tf.Output, limit int64, T tf.DataType) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"limit": limit, "T": T} - opspec := tf.OpSpec{ - Type: "ResourceCountUpTo", - Input: []tf.Input{ - resource, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Delete the stack from its resource container. -// -// Arguments: -// handle: The handle to a stack. -// -// Returns the created operation. -func StackCloseV2(scope *Scope, handle tf.Output) (o *tf.Operation) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "StackCloseV2", - Input: []tf.Input{ - handle, - }, - } - return scope.AddOperation(opspec) -} - -// BatchToSpace for N-D tensors of type T. -// -// This operation reshapes the "batch" dimension 0 into `M + 1` dimensions of shape -// `block_shape + [batch]`, interleaves these blocks back into the grid defined by -// the spatial dimensions `[1, ..., M]`, to obtain a result with the same rank as -// the input. The spatial dimensions of this intermediate result are then -// optionally cropped according to `crops` to produce the output. This is the -// reverse of SpaceToBatch. See below for a precise description. -// -// Arguments: -// input: N-D with shape `input_shape = [batch] + spatial_shape + remaining_shape`, -// where spatial_shape has M dimensions. -// block_shape: 1-D with shape `[M]`, all values must be >= 1. -// crops: 2-D with shape `[M, 2]`, all values must be >= 0. -// `crops[i] = [crop_start, crop_end]` specifies the amount to crop from input -// dimension `i + 1`, which corresponds to spatial dimension `i`. It is -// required that -// `crop_start[i] + crop_end[i] <= block_shape[i] * input_shape[i + 1]`. -// -// This operation is equivalent to the following steps: -// -// 1. Reshape `input` to `reshaped` of shape: -// [block_shape[0], ..., block_shape[M-1], -// batch / prod(block_shape), -// input_shape[1], ..., input_shape[N-1]] -// -// 2. Permute dimensions of `reshaped` to produce `permuted` of shape -// [batch / prod(block_shape), -// -// input_shape[1], block_shape[0], -// ..., -// input_shape[M], block_shape[M-1], -// -// input_shape[M+1], ..., input_shape[N-1]] -// -// 3. Reshape `permuted` to produce `reshaped_permuted` of shape -// [batch / prod(block_shape), -// -// input_shape[1] * block_shape[0], -// ..., -// input_shape[M] * block_shape[M-1], -// -// input_shape[M+1], -// ..., -// input_shape[N-1]] -// -// 4. Crop the start and end of dimensions `[1, ..., M]` of -// `reshaped_permuted` according to `crops` to produce the output of shape: -// [batch / prod(block_shape), -// -// input_shape[1] * block_shape[0] - crops[0,0] - crops[0,1], -// ..., -// input_shape[M] * block_shape[M-1] - crops[M-1,0] - crops[M-1,1], -// -// input_shape[M+1], ..., input_shape[N-1]] -// -// Some examples: -// -// (1) For the following input of shape `[4, 1, 1, 1]`, `block_shape = [2, 2]`, and -// `crops = [[0, 0], [0, 0]]`: -// -// ``` -// [[[[1]]], [[[2]]], [[[3]]], [[[4]]]] -// ``` -// -// The output tensor has shape `[1, 2, 2, 1]` and value: -// -// ``` -// x = [[[[1], [2]], [[3], [4]]]] -// ``` -// -// (2) For the following input of shape `[4, 1, 1, 3]`, `block_shape = [2, 2]`, and -// `crops = [[0, 0], [0, 0]]`: -// -// ``` -// [[[1, 2, 3]], [[4, 5, 6]], [[7, 8, 9]], [[10, 11, 12]]] -// ``` -// -// The output tensor has shape `[1, 2, 2, 3]` and value: -// -// ``` -// x = [[[[1, 2, 3], [4, 5, 6]], -// [[7, 8, 9], [10, 11, 12]]]] -// ``` -// -// (3) For the following input of shape `[4, 2, 2, 1]`, `block_shape = [2, 2]`, and -// `crops = [[0, 0], [0, 0]]`: -// -// ``` -// x = [[[[1], [3]], [[9], [11]]], -// [[[2], [4]], [[10], [12]]], -// [[[5], [7]], [[13], [15]]], -// [[[6], [8]], [[14], [16]]]] -// ``` -// -// The output tensor has shape `[1, 4, 4, 1]` and value: -// -// ``` -// x = [[[1], [2], [3], [4]], -// [[5], [6], [7], [8]], -// [[9], [10], [11], [12]], -// [[13], [14], [15], [16]]] -// ``` -// -// (4) For the following input of shape `[8, 1, 3, 1]`, `block_shape = [2, 2]`, and -// `crops = [[0, 0], [2, 0]]`: -// -// ``` -// x = [[[[0], [1], [3]]], [[[0], [9], [11]]], -// [[[0], [2], [4]]], [[[0], [10], [12]]], -// [[[0], [5], [7]]], [[[0], [13], [15]]], -// [[[0], [6], [8]]], [[[0], [14], [16]]]] -// ``` -// -// The output tensor has shape `[2, 2, 4, 1]` and value: -// -// ``` -// x = [[[[1], [2], [3], [4]], -// [[5], [6], [7], [8]]], -// [[[9], [10], [11], [12]], -// [[13], [14], [15], [16]]]] -// ``` -func BatchToSpaceND(scope *Scope, input tf.Output, block_shape tf.Output, crops tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "BatchToSpaceND", - Input: []tf.Input{ - input, block_shape, crops, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} -- GitLab From 6ed3cec4ae1a0706abf3c7b82f6b70f6a45a760c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 11:55:34 -0700 Subject: [PATCH 086/906] Make GetLocalTemporaryDirectories() a virtual method of Env, that is overriden by the implementations. PiperOrigin-RevId: 190102851 --- tensorflow/core/platform/env.h | 8 ++++++-- tensorflow/core/platform/posix/env.cc | 5 ++++- tensorflow/core/platform/windows/env.cc | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/platform/env.h b/tensorflow/core/platform/env.h index 2a114d47a8..a7e9fcb17c 100644 --- a/tensorflow/core/platform/env.h +++ b/tensorflow/core/platform/env.h @@ -291,10 +291,10 @@ class Env { virtual string FormatLibraryFileName(const string& name, const string& version) = 0; - private: // Returns a possible list of local temporary directories. - void GetLocalTempDirectories(std::vector* list); + virtual void GetLocalTempDirectories(std::vector* list) = 0; + private: std::unique_ptr file_system_registry_; TF_DISALLOW_COPY_AND_ASSIGN(Env); EnvTime* envTime = EnvTime::Default(); @@ -358,6 +358,10 @@ class EnvWrapper : public Env { } private: + void GetLocalTempDirectories(std::vector* list) override { + target_->GetLocalTempDirectories(list); + } + Env* target_; }; diff --git a/tensorflow/core/platform/posix/env.cc b/tensorflow/core/platform/posix/env.cc index 8097624e09..418874d340 100644 --- a/tensorflow/core/platform/posix/env.cc +++ b/tensorflow/core/platform/posix/env.cc @@ -118,6 +118,9 @@ class PosixEnv : public Env { const string& version) override { return tensorflow::internal::FormatLibraryFileName(name, version); } + + private: + void GetLocalTempDirectories(std::vector* list) override; }; } // namespace @@ -131,7 +134,7 @@ Env* Env::Default() { } #endif -void Env::GetLocalTempDirectories(std::vector* list) { +void PosixEnv::GetLocalTempDirectories(std::vector* list) { list->clear(); // Directories, in order of preference. If we find a dir that // exists, we stop adding other less-preferred dirs diff --git a/tensorflow/core/platform/windows/env.cc b/tensorflow/core/platform/windows/env.cc index 41b2644170..2f54f423b2 100644 --- a/tensorflow/core/platform/windows/env.cc +++ b/tensorflow/core/platform/windows/env.cc @@ -160,6 +160,8 @@ class WindowsEnv : public Env { } private: + void GetLocalTempDirectories(std::vector* list) override; + typedef VOID(WINAPI* FnGetSystemTimePreciseAsFileTime)(LPFILETIME); FnGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTime_; }; @@ -174,7 +176,7 @@ Env* Env::Default() { return default_env; } -void Env::GetLocalTempDirectories(std::vector* list) { +void WindowsEnv::GetLocalTempDirectories(std::vector* list) { list->clear(); // On windows we'll try to find a directory in this order: // C:/Documents & Settings/whomever/TEMP (or whatever GetTempPath() is) -- GitLab From c7d11e1601d5045f5421c465a438a1d9632df78d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 12:34:02 -0700 Subject: [PATCH 087/906] Merges predict export_outputs in multi_head. PiperOrigin-RevId: 190108434 --- tensorflow/contrib/estimator/BUILD | 1 + .../estimator/python/estimator/multi_head.py | 9 ++++++ .../python/estimator/multi_head_test.py | 28 +++++++++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index 2f7ed7cd73..676d60231d 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -292,6 +292,7 @@ py_library( "//tensorflow/python:math_ops", "//tensorflow/python:metrics", "//tensorflow/python:summary", + "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:head", "//tensorflow/python/estimator:metric_keys", "//tensorflow/python/estimator:model_fn", diff --git a/tensorflow/contrib/estimator/python/estimator/multi_head.py b/tensorflow/contrib/estimator/python/estimator/multi_head.py index 0346ddc24b..23d3714c53 100644 --- a/tensorflow/contrib/estimator/python/estimator/multi_head.py +++ b/tensorflow/contrib/estimator/python/estimator/multi_head.py @@ -23,6 +23,7 @@ import six from tensorflow.python.estimator import model_fn from tensorflow.python.estimator.canned import head as head_lib from tensorflow.python.estimator.canned import metric_keys +from tensorflow.python.estimator.export import export_output as export_output_lib from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops @@ -319,6 +320,7 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access all_estimator_spec[0].export_outputs, self._heads[0].name), } + merged_predict_outputs = {} for head, spec in zip(self._heads, all_estimator_spec): head_name = head.name for k, v in six.iteritems(spec.export_outputs): @@ -327,8 +329,15 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access else: key = '%s/%s' % (k, head_name) export_outputs[key] = v + if (k == head_lib._PREDICT_SERVING_KEY and # pylint:disable=protected-access + isinstance(v, export_output_lib.PredictOutput)): + for kp, vp in six.iteritems(v.outputs): + key = '%s/%s' % (head_name, kp) + merged_predict_outputs[key] = vp for k, v in six.iteritems(spec.predictions): predictions[(head_name, k)] = v + export_outputs[head_lib._PREDICT_SERVING_KEY] = ( # pylint:disable=protected-access + export_output_lib.PredictOutput(merged_predict_outputs)) return model_fn.EstimatorSpec( mode=model_fn.ModeKeys.PREDICT, diff --git a/tensorflow/contrib/estimator/python/estimator/multi_head_test.py b/tensorflow/contrib/estimator/python/estimator/multi_head_test.py index 65ea89ba1b..8e788a9ce8 100644 --- a/tensorflow/contrib/estimator/python/estimator/multi_head_test.py +++ b/tensorflow/contrib/estimator/python/estimator/multi_head_test.py @@ -127,8 +127,8 @@ class MultiHeadTest(test.TestCase): logits=logits) self.assertItemsEqual( - (_DEFAULT_SERVING_KEY, 'head1', 'classification/head1', 'predict/head1', - 'head2', 'classification/head2', 'predict/head2'), + (_DEFAULT_SERVING_KEY, 'predict', 'head1', 'classification/head1', + 'predict/head1', 'head2', 'classification/head2', 'predict/head2'), spec.export_outputs.keys()) # Assert predictions and export_outputs. @@ -158,6 +158,22 @@ class MultiHeadTest(test.TestCase): self.assertAllClose( expected_probabilities['head2'], sess.run(spec.export_outputs['head2'].scores)) + self.assertAllClose( + expected_probabilities['head1'], + sess.run( + spec.export_outputs['predict'].outputs['head1/probabilities'])) + self.assertAllClose( + expected_probabilities['head2'], + sess.run( + spec.export_outputs['predict'].outputs['head2/probabilities'])) + self.assertAllClose( + expected_probabilities['head1'], + sess.run( + spec.export_outputs['predict/head1'].outputs['probabilities'])) + self.assertAllClose( + expected_probabilities['head2'], + sess.run( + spec.export_outputs['predict/head2'].outputs['probabilities'])) def test_predict_two_heads_logits_tensor(self): """Tests predict with logits as Tensor.""" @@ -181,8 +197,8 @@ class MultiHeadTest(test.TestCase): logits=logits) self.assertItemsEqual( - (_DEFAULT_SERVING_KEY, 'head1', 'classification/head1', 'predict/head1', - 'head2', 'classification/head2', 'predict/head2'), + (_DEFAULT_SERVING_KEY, 'predict', 'head1', 'classification/head1', + 'predict/head1', 'head2', 'classification/head2', 'predict/head2'), spec.export_outputs.keys()) # Assert predictions and export_outputs. @@ -238,8 +254,8 @@ class MultiHeadTest(test.TestCase): logits=logits) self.assertItemsEqual( - (_DEFAULT_SERVING_KEY, 'head1', 'regression/head1', 'predict/head1', - 'head2', 'regression/head2', 'predict/head2'), + (_DEFAULT_SERVING_KEY, 'predict', 'head1', 'regression/head1', + 'predict/head1', 'head2', 'regression/head2', 'predict/head2'), spec.export_outputs.keys()) # Assert predictions and export_outputs. -- GitLab From 830fc390b76b5eb138a7f59d0e13e83add653870 Mon Sep 17 00:00:00 2001 From: Dan Ringwalt Date: Thu, 22 Mar 2018 12:45:24 -0700 Subject: [PATCH 088/906] Add tf.contrib.framework.argsort, wrapping tf.nn.top_k (#288). Comparable to np.argsort. PiperOrigin-RevId: 190109968 --- tensorflow/contrib/framework/__init__.py | 1 + .../contrib/framework/python/ops/sort_ops.py | 161 +++++++++++++----- .../framework/python/ops/sort_ops_test.py | 34 ++++ 3 files changed, 156 insertions(+), 40 deletions(-) diff --git a/tensorflow/contrib/framework/__init__.py b/tensorflow/contrib/framework/__init__.py index 3398b3fd1c..cbb68bd3eb 100644 --- a/tensorflow/contrib/framework/__init__.py +++ b/tensorflow/contrib/framework/__init__.py @@ -83,6 +83,7 @@ See the @{$python/contrib.framework} guide. @@load_linear_multiclass_bias_initializer @@load_variable_slot_initializer +@@argsort @@py_func @@sort diff --git a/tensorflow/contrib/framework/python/ops/sort_ops.py b/tensorflow/contrib/framework/python/ops/sort_ops.py index 8f62f0ea7b..1921a77c1e 100644 --- a/tensorflow/contrib/framework/python/ops/sort_ops.py +++ b/tensorflow/contrib/framework/python/ops/sort_ops.py @@ -14,6 +14,7 @@ # ============================================================================== """Support for sorting tensors. +@@argsort @@sort """ @@ -21,6 +22,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import numpy as np + +from tensorflow.python.framework import constant_op from tensorflow.python.framework import ops as framework_ops from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops @@ -47,64 +51,141 @@ def sort(values, axis=-1, direction='ASCENDING', name=None): ValueError: If axis is not a constant scalar, or the direction is invalid. """ with framework_ops.name_scope(name, 'sort'): - if direction not in _SORT_IMPL: - raise ValueError('%s should be one of %s' % - (direction, ', '.join(sorted(_SORT_IMPL.keys())))) - # Axis must be an integer, not a Tensor. - axis = framework_ops.convert_to_tensor(axis, name='axis') - axis_static = tensor_util.constant_value(axis) - if axis.shape.ndims != 0 or axis_static is None: - raise ValueError('axis must be a constant scalar') - axis_static = int(axis_static) # Avoids NumPy casting error + return _sort_or_argsort(values, axis, direction, return_argsort=False) + + +def argsort(values, axis=-1, direction='ASCENDING', stable=False, name=None): + """Returns the indices of a tensor that give its sorted order along an axis. + + For a 1D tensor, `tf.gather(values, tf.argsort(values))` is equivalent to + `tf.sort(values)`. For higher dimensions, the output has the same shape as + `values`, but along the given axis, values represent the index of the sorted + element in that slice of the tensor at the given position. + + Args: + values: 1-D or higher numeric `Tensor`. + axis: The axis along which to sort. The default is -1, which sorts the last + axis. + direction: The direction in which to sort the values (`'ASCENDING'` or + `'DESCENDING'`). + stable: If True, equal elements in the original tensor will not be + re-ordered in the returned order. Unstable sort is not yet implemented, + but will eventually be the default for performance reasons. If you + require a stable order, pass `stable=True` for forwards compatibility. + name: Optional name for the operation. + + Returns: + An int32 `Tensor` with the same shape as `values`. The indices that would + sort each slice of the given `values` along the given `axis`. + + Raises: + ValueError: If axis is not a constant scalar, or the direction is invalid. + """ + del stable # Unused. + with framework_ops.name_scope(name, 'argsort'): + return _sort_or_argsort(values, axis, direction, return_argsort=True) + + +def _sort_or_argsort(values, axis, direction, return_argsort): + """Internal sort/argsort implementation. + + Args: + values: The input values. + axis: The axis along which to sort. + direction: 'ASCENDING' or 'DESCENDING'. + return_argsort: Whether to return the argsort result. + + Returns: + Either the sorted values, or the indices of the sorted values in the + original tensor. See the `sort` and `argsort` docstrings. + + Raises: + ValueError: If axis is not a constant scalar, or the direction is invalid. + """ + if direction not in _SORT_IMPL: + raise ValueError('%s should be one of %s' % + (direction, ', '.join(sorted(_SORT_IMPL.keys())))) + # Axis must be an integer, not a Tensor. + axis = framework_ops.convert_to_tensor(axis, name='axis') + axis_static = tensor_util.constant_value(axis) + if axis.shape.ndims != 0 or axis_static is None: + raise ValueError('axis must be a constant scalar') + axis_static = int(axis_static) # Avoids NumPy casting error - values = framework_ops.convert_to_tensor(values, name='values') + values = framework_ops.convert_to_tensor(values, name='values') - return _SORT_IMPL[direction](values, axis_static) + return _SORT_IMPL[direction](values, axis_static, return_argsort) -def _descending_sort(values, axis): +def _descending_sort(values, axis, return_argsort=False): """Sorts values in reverse using `top_k`. Args: values: Tensor of numeric values. axis: Index of the axis which values should be sorted along. + return_argsort: If False, return the sorted values. If True, return the + indices that would sort the values. Returns: The sorted values. """ k = array_ops.shape(values)[axis] rank = array_ops.rank(values) + static_rank = values.shape.ndims # Fast path: sorting the last axis. if axis == -1 or axis + 1 == values.get_shape().ndims: - return nn_ops.top_k(values, k)[0] - - # Otherwise, transpose the array. Swap axes `axis` and `rank - 1`. - if axis < 0: - # Make axis a Tensor with the real axis index if needed. - axis += rank - transposition = array_ops.concat( - [ - # Axes up to axis are unchanged. - math_ops.range(axis), - # Swap axis and rank - 1. - [rank - 1], - # Axes in [axis + 1, rank - 1) are unchanged. - math_ops.range(axis + 1, rank - 1), - # Swap axis and rank - 1. - [axis] - ], - axis=0) - top_k_input = array_ops.transpose(values, transposition) - values, unused_indices = nn_ops.top_k(top_k_input, k) - # transposition contains a single cycle of length 2 (swapping 2 elements), - # so it is an involution (it is its own inverse). - return array_ops.transpose(values, transposition) - - -def _ascending_sort(values, axis): + top_k_input = values + transposition = None + else: + # Otherwise, transpose the array. Swap axes `axis` and `rank - 1`. + if axis < 0: + # Calculate the actual axis index if counting from the end. Use the static + # rank if available, or else make the axis back into a tensor. + axis += static_rank or rank + if static_rank is not None: + # Prefer to calculate the transposition array in NumPy and make it a + # constant. + transposition = constant_op.constant( + np.r_[ + # Axes up to axis are unchanged. + np.arange(axis), + # Swap axis and rank - 1. + [static_rank - 1], + # Axes in [axis + 1, rank - 1) are unchanged. + np.arange(axis + 1, static_rank - 1), + # Swap axis and rank - 1. + [axis]], + name='transposition') + else: + # Generate the transposition array from the tensors. + transposition = array_ops.concat( + [ + # Axes up to axis are unchanged. + math_ops.range(axis), + # Swap axis and rank - 1. + [rank - 1], + # Axes in [axis + 1, rank - 1) are unchanged. + math_ops.range(axis + 1, rank - 1), + # Swap axis and rank - 1. + [axis] + ], + axis=0) + top_k_input = array_ops.transpose(values, transposition) + + values, indices = nn_ops.top_k(top_k_input, k) + return_value = indices if return_argsort else values + if transposition is not None: + # transposition contains a single cycle of length 2 (swapping 2 elements), + # so it is an involution (it is its own inverse). + return_value = array_ops.transpose(return_value, transposition) + return return_value + + +def _ascending_sort(values, axis, return_argsort=False): # Negate the values to get the ascending order from descending sort. - values_or_indices = _descending_sort(-values, axis) - return -values_or_indices + values_or_indices = _descending_sort(-values, axis, return_argsort) + # If not argsort, negate the values again. + return values_or_indices if return_argsort else -values_or_indices _SORT_IMPL = { diff --git a/tensorflow/contrib/framework/python/ops/sort_ops_test.py b/tensorflow/contrib/framework/python/ops/sort_ops_test.py index d08ae502f1..a8fb94b245 100644 --- a/tensorflow/contrib/framework/python/ops/sort_ops_test.py +++ b/tensorflow/contrib/framework/python/ops/sort_ops_test.py @@ -24,6 +24,8 @@ from tensorflow.contrib.framework.python.ops import sort_ops 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 tensor_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import random_ops from tensorflow.python.platform import test @@ -90,6 +92,38 @@ class SortTest(test.TestCase): axis=0, direction='DESCENDING').eval()) + def testSort_staticallyKnownRank_constantTransposition(self): + # The transposition array should be a constant if the rank of "values" is + # statically known. + tensor = random_ops.random_uniform( + # Rank is statically known to be 5, but the dimension lengths are not + # known. + random_ops.random_uniform( + shape=(5,), minval=0, maxval=10, dtype=dtypes.int32)) + sort_ops.sort(tensor, axis=1) + transposition = ( + ops.get_default_graph().get_tensor_by_name('sort/transposition:0')) + self.assertFalse(tensor_util.constant_value(transposition) is None) + self.assertAllEqual( + # Swaps "1" and "4" to put "1" at the end. + tensor_util.constant_value(transposition), + [0, 4, 2, 3, 1]) + + def testArgsort_1d(self): + arr = np.random.random(42) + with self.test_session(): + self.assertAllEqual( + np.sort(arr), + array_ops.gather(arr, sort_ops.argsort(arr)).eval()) + + def testArgsort(self): + arr = np.random.random((5, 6, 7, 8)) + for axis in range(4): + with self.test_session(): + self.assertAllEqual( + np.argsort(arr, axis=axis), + sort_ops.argsort(arr, axis=axis).eval()) + if __name__ == '__main__': test.main() -- GitLab From 3158b499c7c811a5ed4b81a2d8341dd3c8923823 Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Thu, 22 Mar 2018 12:52:25 -0700 Subject: [PATCH 089/906] Make api_compatibility_test output verbose by default. PiperOrigin-RevId: 190110866 --- tensorflow/tools/api/tests/api_compatibility_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/api/tests/api_compatibility_test.py b/tensorflow/tools/api/tests/api_compatibility_test.py index 96f501e163..603b2a4327 100644 --- a/tensorflow/tools/api/tests/api_compatibility_test.py +++ b/tensorflow/tools/api/tests/api_compatibility_test.py @@ -58,7 +58,7 @@ _UPDATE_GOLDENS_HELP = """ have to be authorized by TensorFlow leads. """ -# DEFINE_boolean, verbose_diffs, default False: +# DEFINE_boolean, verbose_diffs, default True: _VERBOSE_DIFFS_HELP = """ If set to true, print line by line diffs on all libraries. If set to false, only print which libraries have differences. @@ -286,7 +286,7 @@ if __name__ == '__main__': parser.add_argument( '--update_goldens', type=bool, default=False, help=_UPDATE_GOLDENS_HELP) parser.add_argument( - '--verbose_diffs', type=bool, default=False, help=_VERBOSE_DIFFS_HELP) + '--verbose_diffs', type=bool, default=True, help=_VERBOSE_DIFFS_HELP) FLAGS, unparsed = parser.parse_known_args() # Now update argv, so that unittest library does not get confused. -- GitLab From b57af1577f4a6e4181227d105c68463538b8f9ef Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 13:05:30 -0700 Subject: [PATCH 090/906] Disable testing flaky tensorflow/contrib/eager/python/examples/spinn:spinn_test under py3 PiperOrigin-RevId: 190112748 --- tensorflow/contrib/eager/python/examples/spinn/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/eager/python/examples/spinn/BUILD b/tensorflow/contrib/eager/python/examples/spinn/BUILD index 98d01ad1d5..5966f1d487 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/BUILD +++ b/tensorflow/contrib/eager/python/examples/spinn/BUILD @@ -39,6 +39,7 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", ], tags = [ + "no-internal-py3", # flaky "no_cuda_on_cpu_tap", "no_pip", # because spinn.py is under third_party/. ], -- GitLab From 3642ae00e9268229db76667150c113b83339d11e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 13:21:57 -0700 Subject: [PATCH 091/906] Allow specifying in the arrays extra info file: - the shape of the array - the hardcoding of the values of the array as a single repeated constant scalar value, turning an activations array into a constant array. PiperOrigin-RevId: 190115218 --- .../contrib/lite/toco/model_flags.proto | 4 +++- tensorflow/contrib/lite/toco/tooling_util.cc | 24 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/lite/toco/model_flags.proto b/tensorflow/contrib/lite/toco/model_flags.proto index 867b86f31d..42e0f54826 100644 --- a/tensorflow/contrib/lite/toco/model_flags.proto +++ b/tensorflow/contrib/lite/toco/model_flags.proto @@ -96,11 +96,13 @@ message RnnState { // model that does not already contain such MinMax information. message ArraysExtraInfo { message Entry { - // Next ID to use: 5. + // Next ID to use: 7. optional string name = 1; optional float min = 2; optional float max = 3; optional IODataType data_type = 4; + optional InputArrayShape shape = 5; + optional float constant_float_value = 6; } repeated Entry entries = 1; } diff --git a/tensorflow/contrib/lite/toco/tooling_util.cc b/tensorflow/contrib/lite/toco/tooling_util.cc index ec1770c129..f3f50487ff 100644 --- a/tensorflow/contrib/lite/toco/tooling_util.cc +++ b/tensorflow/contrib/lite/toco/tooling_util.cc @@ -1972,9 +1972,9 @@ void FinishBuildingRNNStates(Model* model) { void UseArraysExtraInfo(Model* model) { for (const auto& entry : model->flags.arrays_extra_info().entries()) { - QCHECK(model->HasArray(entry.name())) - << "ArraysExtraInfo refers to non-existent array name: " - << entry.name(); + if (!model->HasArray(entry.name())) { + continue; + } auto& array = model->GetArray(entry.name()); auto& minmax = array.GetOrCreateMinMax(); if (entry.has_min() || entry.has_max()) { @@ -1986,6 +1986,24 @@ void UseArraysExtraInfo(Model* model) { array.final_data_type = ConvertIODataTypeToArrayDataType(entry.data_type()); } + if (entry.has_shape()) { + array.clear_shape(); + // Make sure to create the shape even if there are no dims, to + // correctly record 0-D shapes. + array.mutable_shape(); + for (int dim : entry.shape().dims()) { + array.mutable_shape()->mutable_dims()->push_back(dim); + } + } + if (entry.has_constant_float_value()) { + CHECK(array.has_shape()); + CHECK(array.data_type == ArrayDataType::kFloat); + auto& data = array.GetMutableBuffer().data; + data.resize(RequiredBufferSizeForShape(array.shape())); + for (float& f : data) { + f = entry.constant_float_value(); + } + } } } -- GitLab From 9b9e54538fa766679aaa60b73f352e975c213730 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Thu, 22 Mar 2018 13:24:51 -0700 Subject: [PATCH 092/906] Disable all the automatic optimizations when testing, to ensure that we can properly compare the results of the original graph against that of the hand optimized graph. PiperOrigin-RevId: 190115606 --- .../core/grappler/utils/grappler_test.cc | 22 +++++++++++++++---- .../core/grappler/utils/grappler_test.h | 7 ++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/grappler/utils/grappler_test.cc b/tensorflow/core/grappler/utils/grappler_test.cc index 6b6cecebe1..1c15ea65b8 100644 --- a/tensorflow/core/grappler/utils/grappler_test.cc +++ b/tensorflow/core/grappler/utils/grappler_test.cc @@ -17,15 +17,30 @@ limitations under the License. #include #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/protobuf/rewriter_config.pb.h" #include "tensorflow/core/public/session.h" namespace tensorflow { namespace grappler { +GrapplerTest::GrapplerTest() { + // Turn off all the automatic optimizations to ensure that we run the graph + // exactly as it is given to us. This ensures that we can compare the results + // before and after manual optimization, without any of the automatic + // optimizations interfering in the comparison. + RewriterConfig* cfg = + options_.config.mutable_graph_options()->mutable_rewrite_options(); + cfg->set_constant_folding(RewriterConfig::OFF); + cfg->set_arithmetic_optimization(RewriterConfig::OFF); + cfg->set_dependency_optimization(RewriterConfig::OFF); + cfg->set_loop_optimization(RewriterConfig::OFF); + cfg->set_function_optimization(RewriterConfig::OFF); + cfg->set_layout_optimizer(RewriterConfig::OFF); +} + std::vector GrapplerTest::EvaluateNodes( const GraphDef& graph, const std::vector& node_names) const { - SessionOptions options; - std::unique_ptr session(NewSession(options)); + std::unique_ptr session(NewSession(options_)); TF_CHECK_OK(session->Create(graph)); RunOptions run_options; std::vector output_tensors; @@ -37,8 +52,7 @@ std::vector GrapplerTest::EvaluateNodes( std::vector GrapplerTest::EvaluateFetchNodes( const GrapplerItem& item) const { - SessionOptions options; - std::unique_ptr session(NewSession(options)); + std::unique_ptr session(NewSession(options_)); TF_CHECK_OK(session->Create(item.graph)); RunOptions run_options; if (!item.init_ops.empty()) { diff --git a/tensorflow/core/grappler/utils/grappler_test.h b/tensorflow/core/grappler/utils/grappler_test.h index c7f06557e7..e0c67381a4 100644 --- a/tensorflow/core/grappler/utils/grappler_test.h +++ b/tensorflow/core/grappler/utils/grappler_test.h @@ -24,11 +24,15 @@ limitations under the License. #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/utils.h" #include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" namespace tensorflow { namespace grappler { class GrapplerTest : public ::testing::Test { + public: + GrapplerTest(); + protected: std::vector EvaluateNodes( const GraphDef& graph, const std::vector& node_names) const; @@ -48,6 +52,9 @@ class GrapplerTest : public ::testing::Test { // Count nodes of the given op-type in a graph. int CountOpNodes(const GraphDef& graph, const string& op); + + private: + SessionOptions options_; }; } // end namespace grappler -- GitLab From f088fa2b1bc010fd4e4396a9f1e6e0868e9890c4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 13:41:13 -0700 Subject: [PATCH 093/906] Code cleanup: rather than storing the outside_compilation shape inference graph as a serialized GraphDef in an attr, put it into the function library. PiperOrigin-RevId: 190118116 --- tensorflow/compiler/jit/BUILD | 1 + .../jit/encapsulate_subgraphs_pass.cc | 67 ++++++++------- .../jit/encapsulate_subgraphs_pass_test.cc | 83 +++++++++---------- 3 files changed, 79 insertions(+), 72 deletions(-) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index 0475cd9ff2..8e505da622 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -348,6 +348,7 @@ tf_cc_test( deps = [ ":common", ":compilation_passes", + ":graph_to_functiondef", "//tensorflow/cc:cc_ops", "//tensorflow/cc:cc_ops_internal", "//tensorflow/cc:function_ops", diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc index 0685036c9d..7fc43fb263 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc @@ -334,8 +334,10 @@ class Encapsulator { void ConnectSequencerToCallNode(Graph* graph_out); Status AddShapeInferenceInfo( + const string& subgraph_name, const string& outside_compilation_subgraph_name, - const std::vector& shapes, GraphDef* inference_graph); + const std::vector& shapes, Graph* inference_graph, + FunctionLibraryDefinition* library); Status ReplaceFunctionDef(FunctionLibraryDefinition* library); @@ -573,7 +575,7 @@ class Encapsulator { const std::unordered_set& recv_at_host_nodes, Node* send_node, FunctionLibraryDefinition* library, std::vector* static_shape_out, - std::unique_ptr* graphdef_out); + std::unique_ptr* graph_out); // Makes a copy of graph containing only nodes that are ancestors of at least // one node in send_from_host_nodes and store it in pruned_graph. On exit @@ -949,8 +951,10 @@ Status Encapsulator::Subgraph::BuildFunctionDef( } Status Encapsulator::Subgraph::AddShapeInferenceInfo( + const string& subgraph_name, const string& outside_compilation_subgraph_name, - const std::vector& shapes, GraphDef* inference_graph) { + const std::vector& shapes, Graph* inference_graph, + FunctionLibraryDefinition* library) { OutsideCompilationSubgraph& oc_subgraph = outside_compilation_subgraphs_.at(outside_compilation_subgraph_name); @@ -972,14 +976,15 @@ Status Encapsulator::Subgraph::AddShapeInferenceInfo( host_compute->AddAttr("shape_inference_graph", ""); host_compute->AddAttr("shapes", shapes); } else { - string serialized_graph; - if (!inference_graph->SerializeToString(&serialized_graph)) { - return errors::Internal( - "Failed to serialize graph for outside compilation subgraph ", - oc_subgraph.host_compute_name); - } - host_compute->AddAttr("shape_inference_graph", serialized_graph); + string inference_graph_name = + strings::StrCat("_outside_compilation_shape_inference_", subgraph_name, + "_", outside_compilation_subgraph_name); + FunctionDef fdef; + TF_RETURN_IF_ERROR( + GraphToFunctionDef(*inference_graph, inference_graph_name, &fdef)); + host_compute->AddAttr("shape_inference_graph", inference_graph_name); host_compute->AddAttr("shapes", std::vector()); + TF_RETURN_IF_ERROR(library->AddFunctionDef(fdef)); } return Status::OK(); } @@ -1760,7 +1765,7 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( const std::unordered_set& recv_at_host_nodes, Node* send_node, FunctionLibraryDefinition* library, std::vector* static_shape_out, - std::unique_ptr* graphdef_out) { + std::unique_ptr* graph_out) { // Maps from nodes in graph_in to nodes in graph_out. // // When an edge has fully defined shape the source node in graph_in is @@ -1777,8 +1782,8 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( std::unordered_map dummy_node_images; std::unordered_map copied_node_images; - std::unique_ptr graph_out(new Graph(graph_in.op_registry())); - graph_out->set_versions(graph_in.versions()); + graph_out->reset(new Graph(graph_in.op_registry())); + (*graph_out)->set_versions(graph_in.versions()); // The final input to the send node is the dynamic key, which we don't include // in the static shapes. static_shape_out->resize(send_node->num_inputs() - 1); @@ -1800,7 +1805,7 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( if (w.leave) { TF_RETURN_IF_ERROR(CopyShapeInferenceNodeToGraph( n, send_node, dummy_node_images, library, &copied_node_images, - graph_out.get())); + graph_out->get())); } else { if (visited[n->id()]) continue; visited[n->id()] = true; @@ -1824,7 +1829,7 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( context->ShapeHandleToProto(shape, &proto); if (dummy_node_images.find(src_node) == dummy_node_images.end()) { dummy_node_images[src_node] = AddDummyShapedNode( - src_node->output_type(src_port), proto, graph_out.get()); + src_node->output_type(src_port), proto, graph_out->get()); } // The final input to the send node is the dynamic key, which we // don't include in the static shapes. @@ -1849,7 +1854,7 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( // The shapes of all the inputs to send_node are statically known. We // won't have to do any inference at compile time so return now: the // shapes were stored in static_shape_out above. - graphdef_out->reset(); + graph_out->reset(); return Status::OK(); } else { // Any shape that is being processed is either the original send node @@ -1872,9 +1877,6 @@ Status Encapsulator::DoStaticShapeInferenceForOutsideCompilationSend( } } - graphdef_out->reset(new GraphDef()); - graph_out->ToGraphDef(graphdef_out->get()); - return Status::OK(); } @@ -1997,13 +1999,14 @@ Status Encapsulator::GetShapeInfoForOutsideCompilationSends( } for (auto& subgraph_entry : subgraphs_) { + const string& subgraph_name = subgraph_entry.first; Subgraph& subgraph = subgraph_entry.second; // Find all the recv_at_host nodes in this subgraph. std::vector outside_compilation_names; subgraph.GetOutsideCompilationSubgraphNames(&outside_compilation_names); std::unordered_set recv_at_host_names; - for (const auto& name : outside_compilation_names) { - Node* recv_node = subgraph.GetRecvAtHostNode(name); + for (const auto& oc_name : outside_compilation_names) { + Node* recv_node = subgraph.GetRecvAtHostNode(oc_name); if (recv_node != nullptr) { recv_at_host_names.insert(recv_node->name()); } @@ -2012,26 +2015,30 @@ Status Encapsulator::GetShapeInfoForOutsideCompilationSends( // without knowing the shape of the recv_at_host nodes, and store the // result, along with enough information to complete the job at compile time // once the recv_at_host shapes are known. - for (const auto& name : outside_compilation_names) { - Node* send_node = subgraph.GetSendFromHostNode(name); + for (const auto& oc_name : outside_compilation_names) { + Node* send_node = subgraph.GetSendFromHostNode(oc_name); std::vector static_shape; - std::unique_ptr graphdef; + std::unique_ptr graph; if (send_node != nullptr) { TF_RETURN_IF_ERROR(DoStaticShapeInferenceForOutsideCompilationSend( *pruned_graph, shape_refiner, recv_at_host_names, - node_images[send_node], library, &static_shape, &graphdef)); - if (graphdef == nullptr) { + node_images[send_node], library, &static_shape, &graph)); + if (graph == nullptr) { VLOG(2) << "Send node " << send_node->name() << " shapes"; for (int i = 0; i < static_shape.size(); ++i) { VLOG(2) << static_shape[i].DebugString(); } } else { - VLOG(2) << "Send node " << send_node->name() << " graph\n" - << graphdef->DebugString(); + if (VLOG_IS_ON(2)) { + GraphDef graphdef; + graph->ToGraphDef(&graphdef); + VLOG(2) << "Send node " << send_node->name() << " graph\n" + << graphdef.DebugString(); + } } } - TF_RETURN_IF_ERROR( - subgraph.AddShapeInferenceInfo(name, static_shape, graphdef.get())); + TF_RETURN_IF_ERROR(subgraph.AddShapeInferenceInfo( + subgraph_name, oc_name, static_shape, graph.get(), library)); } if (!outside_compilation_names.empty()) { TF_RETURN_IF_ERROR(subgraph.ReplaceFunctionDef(library)); diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc index 711b1424c7..94481a1fde 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc @@ -13,12 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include #include #include "tensorflow/compiler/jit/encapsulate_subgraphs_pass.h" #include "tensorflow/cc/framework/ops.h" #include "tensorflow/cc/ops/standard_ops.h" +#include "tensorflow/compiler/jit/graph_to_functiondef.h" #include "tensorflow/core/framework/function_testlib.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/graph/graph_def_builder.h" @@ -32,6 +34,24 @@ namespace { const char* const kXlaHostTransferSequencerAttr = "_xla_host_transfer_sequencer"; +Status AddGraphDefToFunctionLibrary(const GraphDefBuilder& graphdef_builder, + const string& name_suffix, + FunctionDefLibrary* library) { + GraphDef graphdef; + TF_RETURN_IF_ERROR(graphdef_builder.ToGraphDef(&graphdef)); + std::unique_ptr graph = + std::unique_ptr(new Graph(OpRegistry::Global())); + GraphConstructorOptions opts; + opts.allow_internal_ops = true; + TF_RETURN_IF_ERROR(ConvertGraphDefToGraph(opts, graphdef, graph.get())); + FunctionDef* fdef = library->add_function(); + TF_RETURN_IF_ERROR(GraphToFunctionDef( + *graph, + strings::StrCat("_outside_compilation_shape_inference_", name_suffix), + fdef)); + return Status::OK(); +} + template bool EqualProtoMap(const ::tensorflow::protobuf::Map& a, const ::tensorflow::protobuf::Map& b, @@ -115,23 +135,7 @@ bool EqualFunctionNodeDef(const NodeDef& a, const NodeDef& b, a.attr(), b.attr(), [](const string& s) { return s; }, [](const AttrValue& v) { return v.DebugString(); }, [](const string& key, const AttrValue& av, const AttrValue& bv) { - if (key == "shape_inference_graph") { - // Default serialization of GraphDef is unstable because maps don't - // serialize deterministically. Rather than go through the hoops to - // turn on deterministic serialization of this attr just for this - // test, add logic here to compare determinstically. - GraphDef ga; - if (!ga.ParseFromString(av.s())) { - return false; - } - GraphDef gb; - if (!gb.ParseFromString(bv.s())) { - return false; - } - return EqualGraphDef(ga, gb, nullptr); - } else { - return av.DebugString() == bv.DebugString(); - } + return av.DebugString() == bv.DebugString(); }, strings::StrCat(diff_preamble, " attr mismatch for node ", a.name()), diff); @@ -848,7 +852,6 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { FunctionDefLibrary library_expected; GraphDef graphdef_expected; - string shape_string_expected; { GraphDefBuilder shape(GraphDefBuilder::kFailImmediately); Node* key_constant = @@ -861,9 +864,8 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { shape.opts().WithName("E")); SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); - GraphDef shape_graph; - TF_EXPECT_OK(shape.ToGraphDef(&shape_graph)); - EXPECT_TRUE(shape_graph.SerializeToString(&shape_string_expected)); + TF_EXPECT_OK( + AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } *library_expected.add_function() = test::function::XTimesTwo(); @@ -883,7 +885,8 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { {{"Tinputs", gtl::ArraySlice({DT_FLOAT, DT_FLOAT})}, {"Toutputs", gtl::ArraySlice({DT_FLOAT})}, {"key", "host_compute_channel_F1_O1"}, - {"shape_inference_graph", shape_string_expected}, + {"shape_inference_graph", + "_outside_compilation_shape_inference_F1_O1"}, {"shapes", gtl::ArraySlice({})}}, {"c"}}, }, @@ -969,7 +972,6 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { FunctionDefLibrary library_expected; GraphDef graphdef_expected; - string shape_string_expected_1; { GraphDefBuilder shape1(GraphDefBuilder::kFailImmediately); Node* key_constant = @@ -982,12 +984,10 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { shape1.opts().WithName("E")); SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, shape1.opts().WithName("outside_compilation_F1_O1_send")); - GraphDef shape1_graph; - TF_EXPECT_OK(shape1.ToGraphDef(&shape1_graph)); - EXPECT_TRUE(shape1_graph.SerializeToString(&shape_string_expected_1)); + TF_EXPECT_OK( + AddGraphDefToFunctionLibrary(shape1, "F1_O1", &library_expected)); } - string shape_string_expected_2; { GraphDefBuilder shape2(GraphDefBuilder::kFailImmediately); Node* key_constant = @@ -1005,9 +1005,8 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { Node* h = Binary(ops::NodeOut(recv2, 0), e, shape2.opts().WithName("H")); SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O2", {h}, shape2.opts().WithName("outside_compilation_F1_O2_send")); - GraphDef shape2_graph; - TF_EXPECT_OK(shape2.ToGraphDef(&shape2_graph)); - EXPECT_TRUE(shape2_graph.SerializeToString(&shape_string_expected_2)); + TF_EXPECT_OK( + AddGraphDefToFunctionLibrary(shape2, "F1_O2", &library_expected)); } *library_expected.add_function() = FunctionDefHelper::Create( @@ -1029,7 +1028,8 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { {{"Tinputs", gtl::ArraySlice({DT_FLOAT, DT_FLOAT})}, {"Toutputs", gtl::ArraySlice({DT_FLOAT})}, {"key", "host_compute_channel_F1_O2"}, - {"shape_inference_graph", shape_string_expected_2}, + {"shape_inference_graph", + "_outside_compilation_shape_inference_F1_O2"}, {"shapes", gtl::ArraySlice({})}}, {"F"}}, {{"outside_compilation_O1_host_compute"}, @@ -1038,7 +1038,8 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { {{"Tinputs", gtl::ArraySlice({DT_FLOAT, DT_FLOAT})}, {"Toutputs", gtl::ArraySlice({DT_FLOAT})}, {"key", "host_compute_channel_F1_O1"}, - {"shape_inference_graph", shape_string_expected_1}, + {"shape_inference_graph", + "_outside_compilation_shape_inference_F1_O1"}, {"shapes", gtl::ArraySlice({})}}, {"D"}}, }, @@ -1134,7 +1135,6 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { FunctionDefLibrary library_expected; GraphDef graphdef_expected; - string shape_string_expected; { GraphDefBuilder shape(GraphDefBuilder::kFailImmediately); Node* key_constant = @@ -1147,9 +1147,8 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { shape.opts().WithName("E")); SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); - GraphDef shape_graph; - TF_EXPECT_OK(shape.ToGraphDef(&shape_graph)); - EXPECT_TRUE(shape_graph.SerializeToString(&shape_string_expected)); + TF_EXPECT_OK( + AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } TensorShapeProto shape_proto_expected; @@ -1172,7 +1171,8 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { {{"Tinputs", gtl::ArraySlice({DT_FLOAT, DT_FLOAT})}, {"Toutputs", gtl::ArraySlice({DT_FLOAT})}, {"key", "host_compute_channel_F1_O1"}, - {"shape_inference_graph", shape_string_expected}, + {"shape_inference_graph", + "_outside_compilation_shape_inference_F1_O1"}, {"shapes", gtl::ArraySlice({})}}, {"D"}}, }, @@ -1661,7 +1661,6 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { FunctionDefLibrary library_expected; GraphDef graphdef_expected; - string shape_string_expected; { GraphDefBuilder shape(GraphDefBuilder::kFailImmediately); Node* key_constant = @@ -1673,9 +1672,8 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { Node* e = BinaryUnknownShape(known, recv, shape.opts().WithName("E")); SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); - GraphDef shape_graph; - TF_EXPECT_OK(shape.ToGraphDef(&shape_graph)); - EXPECT_TRUE(shape_graph.SerializeToString(&shape_string_expected)); + TF_EXPECT_OK( + AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } *library_expected.add_function() = test::function::XTimesTwo(); @@ -1694,7 +1692,8 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { {{"Tinputs", gtl::ArraySlice({DT_FLOAT})}, {"Toutputs", gtl::ArraySlice({DT_FLOAT})}, {"key", "host_compute_channel_F1_O1"}, - {"shape_inference_graph", shape_string_expected}, + {"shape_inference_graph", + "_outside_compilation_shape_inference_F1_O1"}, {"shapes", gtl::ArraySlice({})}}, {"c"}}, }, -- GitLab From f2b62548edfd298367bc996fb236ea39f385ff76 Mon Sep 17 00:00:00 2001 From: Anna R Date: Thu, 22 Mar 2018 13:55:48 -0700 Subject: [PATCH 094/906] Fix cases where we export incorrect symbol with tf_export. This can happen when both generated op and its python wrapper have tf_export decorator. create_python_api.py now checks that we don't export different symbols with same name. Also, simplified some logic. PiperOrigin-RevId: 190120505 --- .../api_def/python_api/api_def_ArgMax.pbtxt | 4 + .../api_def/python_api/api_def_ArgMin.pbtxt | 4 + .../python_api/api_def_CountUpTo.pbtxt | 4 + .../core/api_def/python_api/api_def_Div.pbtxt | 4 + .../core/api_def/python_api/api_def_Erf.pbtxt | 4 + .../api_def/python_api/api_def_Identity.pbtxt | 4 + .../core/api_def/python_api/api_def_Mod.pbtxt | 4 + .../api_def/python_api/api_def_Rank.pbtxt | 4 + .../api_def/python_api/api_def_Round.pbtxt | 4 + .../python_api/api_def_ScatterNdUpdate.pbtxt | 4 + .../python_api/api_def_ScatterUpdate.pbtxt | 4 + .../api_def/python_api/api_def_ShapeN.pbtxt | 4 + .../api_def/python_api/api_def_Sign.pbtxt | 4 + .../api_def/python_api/api_def_Sqrt.pbtxt | 4 + .../api_def/python_api/api_def_Square.pbtxt | 4 + tensorflow/python/framework/python_op_gen.cc | 8 +- tensorflow/python/ops/math_ops.py | 4 +- .../tools/api/generator/create_python_api.py | 82 ++++++++++++++----- 18 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 tensorflow/core/api_def/python_api/api_def_ArgMax.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ArgMin.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CountUpTo.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Div.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Erf.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Identity.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Mod.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Rank.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Round.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ScatterNdUpdate.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ScatterUpdate.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ShapeN.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Sign.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Sqrt.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_Square.pbtxt diff --git a/tensorflow/core/api_def/python_api/api_def_ArgMax.pbtxt b/tensorflow/core/api_def/python_api/api_def_ArgMax.pbtxt new file mode 100644 index 0000000000..4c23a432f2 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ArgMax.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ArgMax" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ArgMin.pbtxt b/tensorflow/core/api_def/python_api/api_def_ArgMin.pbtxt new file mode 100644 index 0000000000..daa14f6386 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ArgMin.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ArgMin" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_CountUpTo.pbtxt b/tensorflow/core/api_def/python_api/api_def_CountUpTo.pbtxt new file mode 100644 index 0000000000..f41be2f540 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_CountUpTo.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "CountUpTo" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Div.pbtxt b/tensorflow/core/api_def/python_api/api_def_Div.pbtxt new file mode 100644 index 0000000000..8e5537c8bf --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Div.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Div" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Erf.pbtxt b/tensorflow/core/api_def/python_api/api_def_Erf.pbtxt new file mode 100644 index 0000000000..391167254e --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Erf.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Erf" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Identity.pbtxt b/tensorflow/core/api_def/python_api/api_def_Identity.pbtxt new file mode 100644 index 0000000000..00f2afde27 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Identity.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Identity" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Mod.pbtxt b/tensorflow/core/api_def/python_api/api_def_Mod.pbtxt new file mode 100644 index 0000000000..48d828ca72 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Mod.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Mod" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Rank.pbtxt b/tensorflow/core/api_def/python_api/api_def_Rank.pbtxt new file mode 100644 index 0000000000..05aa12f2fa --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Rank.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Rank" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Round.pbtxt b/tensorflow/core/api_def/python_api/api_def_Round.pbtxt new file mode 100644 index 0000000000..74428e2f58 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Round.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Round" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ScatterNdUpdate.pbtxt b/tensorflow/core/api_def/python_api/api_def_ScatterNdUpdate.pbtxt new file mode 100644 index 0000000000..ccf4a9cce8 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ScatterNdUpdate.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ScatterNdUpdate" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ScatterUpdate.pbtxt b/tensorflow/core/api_def/python_api/api_def_ScatterUpdate.pbtxt new file mode 100644 index 0000000000..e4c41c1226 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ScatterUpdate.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ScatterUpdate" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ShapeN.pbtxt b/tensorflow/core/api_def/python_api/api_def_ShapeN.pbtxt new file mode 100644 index 0000000000..b2dbe74b09 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ShapeN.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ShapeN" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Sign.pbtxt b/tensorflow/core/api_def/python_api/api_def_Sign.pbtxt new file mode 100644 index 0000000000..c2ee91dd12 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Sign.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Sign" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Sqrt.pbtxt b/tensorflow/core/api_def/python_api/api_def_Sqrt.pbtxt new file mode 100644 index 0000000000..59e2dfe836 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Sqrt.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Sqrt" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_Square.pbtxt b/tensorflow/core/api_def/python_api/api_def_Square.pbtxt new file mode 100644 index 0000000000..7b39ae25fa --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_Square.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "Square" + visibility: HIDDEN +} diff --git a/tensorflow/python/framework/python_op_gen.cc b/tensorflow/python/framework/python_op_gen.cc index 03721c9a68..9850f0becc 100644 --- a/tensorflow/python/framework/python_op_gen.cc +++ b/tensorflow/python/framework/python_op_gen.cc @@ -78,7 +78,7 @@ bool IsPythonReserved(const string& s) { bool IsOpWithUnderscorePrefix(const string& s) { static const std::set* const kUnderscoreOps = new std::set( {// Lowercase built-in functions and types in Python, from: - // [x for x in dir(__builtins__) if x[0].islower()] + // [x for x in dir(__builtins__) if x[0].islower()] except "round". // These need to be excluded so they don't conflict with actual built-in // functions since we use '*' imports. "abs", "all", "any", "apply", "bin", "bool", "buffer", "bytearray", @@ -90,9 +90,9 @@ bool IsOpWithUnderscorePrefix(const string& s) { "iter", "len", "license", "list", "locals", "long", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "quit", "range", "raw_input", "reduce", "reload", - "repr", "reversed", "round", "set", "setattr", "slice", "sorted", - "staticmethod", "str", "sum", "super", "tuple", "type", "unichr", - "unicode", "vars", "xrange", "zip", + "repr", "reversed", "set", "setattr", "slice", "sorted", "staticmethod", + "str", "sum", "super", "tuple", "type", "unichr", "unicode", "vars", + "xrange", "zip", // These have the same name as ops defined in Python and might be used // incorrectly depending on order of '*' imports. // TODO(annarev): reduce usage of '*' imports and remove these from the diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index c893bf9b90..4699e05269 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -180,6 +180,8 @@ linspace = gen_math_ops.lin_space arg_max = deprecation.deprecated(None, "Use `argmax` instead")(arg_max) # pylint: disable=used-before-assignment arg_min = deprecation.deprecated(None, "Use `argmin` instead")(arg_min) # pylint: disable=used-before-assignment +tf_export("arg_max")(arg_max) +tf_export("arg_min")(arg_min) # This is set by resource_variable_ops.py. It is included in this way since @@ -1196,7 +1198,7 @@ tf_export("floor_div")(floor_div) truncatemod = gen_math_ops.truncate_mod tf_export("truncatemod")(truncatemod) floormod = gen_math_ops.floor_mod -tf_export("floormod")(floormod) +tf_export("floormod", "mod")(floormod) def _mul_dispatch(x, y, name=None): diff --git a/tensorflow/tools/api/generator/create_python_api.py b/tensorflow/tools/api/generator/create_python_api.py index bb7c3e77a3..183c4731b8 100644 --- a/tensorflow/tools/api/generator/create_python_api.py +++ b/tensorflow/tools/api/generator/create_python_api.py @@ -23,7 +23,6 @@ import collections import os import sys -from tensorflow import python as tf from tensorflow.python.util import tf_decorator @@ -39,6 +38,11 @@ Generated by: tensorflow/tools/api/generator/create_python_api.py script. """ +class SymbolExposedTwiceError(Exception): + """Raised when different symbols are exported with the same name.""" + pass + + def format_import(source_module_name, source_name, dest_name): """Formats import statement. @@ -63,6 +67,44 @@ def format_import(source_module_name, source_name, dest_name): return 'import %s as %s' % (source_name, dest_name) +class _ModuleImportsBuilder(object): + """Builds a map from module name to imports included in that module.""" + + def __init__(self): + self.module_imports = collections.defaultdict(list) + self._seen_api_names = set() + + def add_import( + self, dest_module_name, source_module_name, source_name, dest_name): + """Adds this import to module_imports. + + Args: + dest_module_name: (string) Module name to add import to. + source_module_name: (string) Module to import from. + source_name: (string) Name of the symbol to import. + dest_name: (string) Import the symbol using this name. + + Raises: + SymbolExposedTwiceError: Raised when an import with the same + dest_name has already been added to dest_module_name. + """ + import_str = format_import(source_module_name, source_name, dest_name) + if import_str in self.module_imports[dest_module_name]: + return + + # Check if we are trying to expose two different symbols with same name. + full_api_name = dest_name + if dest_module_name: + full_api_name = dest_module_name + '.' + full_api_name + if full_api_name in self._seen_api_names: + raise SymbolExposedTwiceError( + 'Trying to export multiple symbols with same name: %s.' % + full_api_name) + self._seen_api_names.add(full_api_name) + + self.module_imports[dest_module_name].append(import_str) + + def get_api_imports(): """Get a map from destination module to formatted imports. @@ -73,7 +115,9 @@ def get_api_imports(): (for e.g. 'from foo import bar') and constant assignments (for e.g. 'FOO = 123'). """ - module_imports = collections.defaultdict(list) + module_imports_builder = _ModuleImportsBuilder() + visited_symbols = set() + # Traverse over everything imported above. Specifically, # we want to traverse over TensorFlow Python modules. for module in sys.modules.values(): @@ -86,6 +130,8 @@ def get_api_imports(): for module_contents_name in dir(module): attr = getattr(module, module_contents_name) + if id(attr) in visited_symbols: + continue # If attr is _tf_api_constants attribute, then add the constants. if module_contents_name == _API_CONSTANTS_ATTR: @@ -93,36 +139,30 @@ def get_api_imports(): for export in exports: names = export.split('.') dest_module = '.'.join(names[:-1]) - import_str = format_import(module.__name__, value, names[-1]) - module_imports[dest_module].append(import_str) + module_imports_builder.add_import( + dest_module, module.__name__, value, names[-1]) continue _, attr = tf_decorator.unwrap(attr) # If attr is a symbol with _tf_api_names attribute, then # add import for it. if hasattr(attr, '__dict__') and _API_NAMES_ATTR in attr.__dict__: - # The same op might be accessible from multiple modules. - # We only want to consider location where function was defined. - # Here we check if the op is defined in another TensorFlow module in - # sys.modules. - if (hasattr(attr, '__module__') and - attr.__module__.startswith(tf.__name__) and - attr.__module__ != module.__name__ and - attr.__module__ in sys.modules and - module_contents_name in dir(sys.modules[attr.__module__])): + # If the same symbol is available using multiple names, only create + # imports for it once. + if id(attr) in visited_symbols: continue + visited_symbols.add(id(attr)) for export in attr._tf_api_names: # pylint: disable=protected-access names = export.split('.') dest_module = '.'.join(names[:-1]) - import_str = format_import( - module.__name__, module_contents_name, names[-1]) - module_imports[dest_module].append(import_str) + module_imports_builder.add_import( + dest_module, module.__name__, module_contents_name, names[-1]) # Import all required modules in their parent modules. # For e.g. if we import 'foo.bar.Value'. Then, we also # import 'bar' in 'foo'. - imported_modules = set(module_imports.keys()) + imported_modules = set(module_imports_builder.module_imports.keys()) for module in imported_modules: if not module: continue @@ -135,13 +175,11 @@ def get_api_imports(): parent_module += ('.' + module_split[submodule_index-1] if parent_module else module_split[submodule_index-1]) import_from += '.' + parent_module - submodule_import = format_import( - import_from, module_split[submodule_index], + module_imports_builder.add_import( + parent_module, import_from, module_split[submodule_index], module_split[submodule_index]) - if submodule_import not in module_imports[parent_module]: - module_imports[parent_module].append(submodule_import) - return module_imports + return module_imports_builder.module_imports def create_api_files(output_files): -- GitLab From 804f98e5bc0a75284f5f92569e5c82fe88b455ad Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 22 Mar 2018 14:01:10 -0700 Subject: [PATCH 095/906] Supports optimizer arg in head.create_estimator_spec. PiperOrigin-RevId: 190121386 --- tensorflow/contrib/estimator/BUILD | 2 + .../estimator/python/estimator/head.py | 28 +++- .../estimator/python/estimator/head_test.py | 36 +++++ .../estimator/python/estimator/multi_head.py | 34 +++-- .../python/estimator/multi_head_test.py | 38 ++++++ tensorflow/python/estimator/BUILD | 2 +- tensorflow/python/estimator/canned/dnn.py | 9 +- .../estimator/canned/dnn_testing_utils.py | 15 ++- tensorflow/python/estimator/canned/head.py | 126 ++++++++++++------ .../python/estimator/canned/head_test.py | 102 ++++++++++++++ tensorflow/python/estimator/canned/linear.py | 9 +- 11 files changed, 322 insertions(+), 79 deletions(-) diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index 676d60231d..24374266dc 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -175,6 +175,7 @@ py_library( "//tensorflow/python:sparse_ops", "//tensorflow/python:sparse_tensor", "//tensorflow/python:summary", + "//tensorflow/python:training", "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:head", "//tensorflow/python/estimator:metric_keys", @@ -292,6 +293,7 @@ py_library( "//tensorflow/python:math_ops", "//tensorflow/python:metrics", "//tensorflow/python:summary", + "//tensorflow/python:training", "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:head", "//tensorflow/python/estimator:metric_keys", diff --git a/tensorflow/contrib/estimator/python/estimator/head.py b/tensorflow/contrib/estimator/python/estimator/head.py index f95fcc8039..42e1b7b68c 100644 --- a/tensorflow/contrib/estimator/python/estimator/head.py +++ b/tensorflow/contrib/estimator/python/estimator/head.py @@ -36,10 +36,12 @@ from tensorflow.python.ops import sparse_ops from tensorflow.python.ops.losses import losses from tensorflow.python.saved_model import signature_constants from tensorflow.python.summary import summary +from tensorflow.python.training import training_util _DEFAULT_SERVING_KEY = signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY +# TODO(b/65403806): Switch loss_reduction default to SUM_OVER_BATCH_SIZE. def multi_class_head(n_classes, weight_column=None, label_vocabulary=None, @@ -489,8 +491,8 @@ class _MultiLabelHead(head_lib._Head): # pylint:disable=protected-access processed_labels=processed_labels) def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None, - regularization_losses=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None, regularization_losses=None): """Returns an `EstimatorSpec`. Args: @@ -502,8 +504,11 @@ class _MultiLabelHead(head_lib._Head): # pylint:disable=protected-access with shape `[D0, D1, ... DN, n_classes]` or `SparseTensor` with `dense_shape` `[D0, D1, ... DN, ?]`. `labels` is required argument when `mode` equals `TRAIN` or `EVAL`. + optimizer: `Optimizer` instance to optimize the loss in TRAIN mode. + Namely, sets `train_op = optimizer.minimize(loss, global_step)`, which + updates variables and increments `global_step`. train_op_fn: Function that takes a scalar loss `Tensor` and returns - `train_op`. Required in TRAIN mode. + `train_op`. Used if `optimizer` is `None`. regularization_losses: A list of additional scalar losses to be added to the training loss, such as regularization losses. These losses are usually expressed as a batch average, so for best results users need to @@ -513,7 +518,8 @@ class _MultiLabelHead(head_lib._Head): # pylint:disable=protected-access Returns: `EstimatorSpec`. Raises: - ValueError: If `train_op_fn` is `None` in TRAIN mode. + ValueError: If both `train_op_fn` and `optimizer` are `None` in TRAIN + mode, or if both are set. """ with ops.name_scope(self._name, 'head'): logits = head_lib._check_logits_final_dim(logits, self.logits_dimension) # pylint:disable=protected-access @@ -565,8 +571,16 @@ class _MultiLabelHead(head_lib._Head): # pylint:disable=protected-access regularization_loss=regularization_loss)) # Train. - if train_op_fn is None: - raise ValueError('train_op_fn can not be None.') + if optimizer is not None: + if train_op_fn is not None: + raise ValueError('train_op_fn and optimizer cannot both be set.') + train_op = optimizer.minimize( + regularized_training_loss, + global_step=training_util.get_global_step()) + elif train_op_fn is not None: + train_op = train_op_fn(regularized_training_loss) + else: + raise ValueError('train_op_fn and optimizer cannot both be None.') # Only summarize mean_loss for SUM reduction to preserve backwards # compatibility. Otherwise skip it to avoid unnecessary computation. if self._loss_reduction == losses.Reduction.SUM: @@ -592,7 +606,7 @@ class _MultiLabelHead(head_lib._Head): # pylint:disable=protected-access mode=model_fn.ModeKeys.TRAIN, predictions=predictions, loss=regularized_training_loss, - train_op=train_op_fn(regularized_training_loss)) + train_op=train_op) def _eval_metric_ops( self, labels, probabilities, weights, unreduced_loss, diff --git a/tensorflow/contrib/estimator/python/estimator/head_test.py b/tensorflow/contrib/estimator/python/estimator/head_test.py index dc30dde877..776f0ee341 100644 --- a/tensorflow/contrib/estimator/python/estimator/head_test.py +++ b/tensorflow/contrib/estimator/python/estimator/head_test.py @@ -863,6 +863,42 @@ class MultiLabelHead(test.TestCase): self._test_train( head=head, logits=logits, labels=labels, expected_loss=expected_loss) + def test_train_with_optimizer(self): + head = head_lib.multi_label_head(n_classes=2) + logits = np.array([[-10., 10.], [-15., 10.]], dtype=np.float32) + labels = np.array([[1, 0], [1, 1]], dtype=np.int64) + # For large logits, sigmoid cross entropy loss is approximated as: + # loss = labels * (logits < 0) * (-logits) + + # (1 - labels) * (logits > 0) * logits => + # expected_unweighted_loss = [[10., 10.], [15., 0.]] + # Average over classes, sum over weights. + expected_loss = 17.5 + expected_train_result = 'my_train_op' + + class _Optimizer(object): + + def minimize(self, loss, global_step): + del global_step + return string_ops.string_join( + [constant_op.constant(expected_train_result), + string_ops.as_string(loss, precision=3)]) + + spec = head.create_estimator_spec( + features={'x': np.array(((42,),), dtype=np.int32)}, + mode=model_fn.ModeKeys.TRAIN, + logits=logits, + labels=labels, + optimizer=_Optimizer()) + + tol = 1e-3 + with self.test_session() as sess: + _initialize_variables(self, spec.scaffold) + loss, train_result = sess.run((spec.loss, spec.train_op)) + self.assertAllClose(expected_loss, loss, rtol=tol, atol=tol) + self.assertEqual( + six.b('{0:s}{1:.3f}'.format(expected_train_result, expected_loss)), + train_result) + def test_train_with_regularization_losses(self): head = head_lib.multi_label_head( n_classes=2, loss_reduction=losses.Reduction.SUM_OVER_BATCH_SIZE) diff --git a/tensorflow/contrib/estimator/python/estimator/multi_head.py b/tensorflow/contrib/estimator/python/estimator/multi_head.py index 23d3714c53..bbbc19cc4d 100644 --- a/tensorflow/contrib/estimator/python/estimator/multi_head.py +++ b/tensorflow/contrib/estimator/python/estimator/multi_head.py @@ -31,6 +31,7 @@ from tensorflow.python.ops import math_ops from tensorflow.python.ops import metrics as metrics_lib from tensorflow.python.saved_model import signature_constants from tensorflow.python.summary import summary +from tensorflow.python.training import training_util _DEFAULT_SERVING_KEY = signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY @@ -227,8 +228,10 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access weights=example_weights_by_head, processed_labels=labels_by_head) + # TODO(b/65403806): Support regularization_losses arg. def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None): """See `_Head`.""" if isinstance(logits, dict): logits_dict = logits @@ -249,9 +252,10 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access train_op_fn=_no_op_train_fn)) if mode == model_fn.ModeKeys.TRAIN: - if train_op_fn is None: - raise ValueError('train_op_fn can not be None in TRAIN mode.') - spec = self._merge_train(all_estimator_spec, train_op_fn) + spec = self._merge_train( + all_estimator_spec=all_estimator_spec, + optimizer=optimizer, + train_op_fn=train_op_fn) with ops.name_scope(''): summary.scalar(metric_keys.MetricKeys.LOSS, spec.loss) return spec @@ -280,16 +284,21 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access begin_idx += head.logits_dimension return logits_dict - def _merge_train(self, all_estimator_spec, train_op_fn): + def _merge_train(self, all_estimator_spec, optimizer, train_op_fn): """Merges list of `EstimatorSpec` for training. Args: all_estimator_spec: list of `EstimatorSpec` for the individual heads. - train_op_fn: Function to create train op. See `create_estimator_spec` - documentation for more details. + optimizer: `Optimizer` instance to create train op. See + `create_estimator_spec` documentation for more details. + train_op_fn: Function to create train op. Used if `optimizer` is `None`. Returns: `EstimatorSpec` that merges all heads for TRAIN. + + Raises: + ValueError: If both `train_op_fn` and `optimizer` are `None` in TRAIN + mode. """ losses = [] metrics = {} @@ -298,11 +307,20 @@ class _MultiHead(head_lib._Head): # pylint:disable=protected-access # Metric keys already contain head.name. metrics.update(spec.eval_metric_ops or {}) loss = _merge_losses(losses, self._head_weights) + if optimizer is not None: + if train_op_fn is not None: + raise ValueError('train_op_fn and optimizer cannot both be set.') + train_op = optimizer.minimize( + loss, global_step=training_util.get_global_step()) + elif train_op_fn is not None: + train_op = train_op_fn(loss) + else: + raise ValueError('train_op_fn and optimizer cannot both be None.') return model_fn.EstimatorSpec( mode=model_fn.ModeKeys.TRAIN, loss=loss, - train_op=train_op_fn(loss), + train_op=train_op, eval_metric_ops=metrics) def _merge_predict(self, all_estimator_spec): diff --git a/tensorflow/contrib/estimator/python/estimator/multi_head_test.py b/tensorflow/contrib/estimator/python/estimator/multi_head_test.py index 8e788a9ce8..43cc157a1f 100644 --- a/tensorflow/contrib/estimator/python/estimator/multi_head_test.py +++ b/tensorflow/contrib/estimator/python/estimator/multi_head_test.py @@ -550,6 +550,44 @@ class MultiHeadTest(test.TestCase): metric_keys.MetricKeys.LOSS_MEAN + '/head1': expected_loss / 2, }, summary_str, tol) + def test_train_one_head_with_optimizer(self): + head1 = head_lib.multi_label_head(n_classes=2, name='head1') + multi_head = multi_head_lib.multi_head([head1]) + + logits = {'head1': np.array([[-10., 10.], [-15., 10.]], dtype=np.float32)} + labels = {'head1': np.array([[1, 0], [1, 1]], dtype=np.int64)} + # For large logits, sigmoid cross entropy loss is approximated as: + # loss = labels * (logits < 0) * (-logits) + + # (1 - labels) * (logits > 0) * logits => + # expected_unweighted_loss = [[10., 10.], [15., 0.]] + # Average over classes, sum over weights. + expected_loss = 17.5 + expected_train_result = 'my_train_op' + + class _Optimizer(object): + + def minimize(self, loss, global_step): + del global_step + return string_ops.string_join( + [constant_op.constant(expected_train_result), + string_ops.as_string(loss, precision=3)]) + + spec = multi_head.create_estimator_spec( + features={'x': np.array(((42,),), dtype=np.int32)}, + mode=model_fn.ModeKeys.TRAIN, + logits=logits, + labels=labels, + optimizer=_Optimizer()) + + tol = 1e-3 + with self.test_session() as sess: + _initialize_variables(self, spec.scaffold) + loss, train_result = sess.run((spec.loss, spec.train_op)) + self.assertAllClose(expected_loss, loss, rtol=tol, atol=tol) + self.assertEqual( + six.b('{0:s}{1:.3f}'.format(expected_train_result, expected_loss)), + train_result) + def test_train_two_heads_with_weights(self): head1 = head_lib.multi_label_head(n_classes=2, name='head1') head2 = head_lib.multi_label_head(n_classes=3, name='head2') diff --git a/tensorflow/python/estimator/BUILD b/tensorflow/python/estimator/BUILD index b25f9d2153..5afb5a7dd5 100644 --- a/tensorflow/python/estimator/BUILD +++ b/tensorflow/python/estimator/BUILD @@ -265,7 +265,6 @@ py_library( "//tensorflow/python:nn", "//tensorflow/python:partitioned_variables", "//tensorflow/python:summary", - "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python/feature_column", "//tensorflow/python/ops/losses", @@ -617,6 +616,7 @@ py_library( "//tensorflow/python:sparse_tensor", "//tensorflow/python:string_ops", "//tensorflow/python:summary", + "//tensorflow/python:training", "//tensorflow/python:weights_broadcast_ops", "//tensorflow/python/feature_column", "//tensorflow/python/ops/losses", diff --git a/tensorflow/python/estimator/canned/dnn.py b/tensorflow/python/estimator/canned/dnn.py index 7043da8de0..6382622e0b 100644 --- a/tensorflow/python/estimator/canned/dnn.py +++ b/tensorflow/python/estimator/canned/dnn.py @@ -32,7 +32,6 @@ from tensorflow.python.ops import partitioned_variables from tensorflow.python.ops import variable_scope from tensorflow.python.ops.losses import losses from tensorflow.python.summary import summary -from tensorflow.python.training import training_util from tensorflow.python.util.tf_export import tf_export # The default learning rate of 0.05 is a historical artifact of the initial @@ -183,17 +182,11 @@ def _dnn_model_fn(features, input_layer_partitioner=input_layer_partitioner) logits = logit_fn(features=features, mode=mode) - def _train_op_fn(loss): - """Returns the op to optimize the loss.""" - return optimizer.minimize( - loss, - global_step=training_util.get_global_step()) - return head.create_estimator_spec( features=features, mode=mode, labels=labels, - train_op_fn=_train_op_fn, + optimizer=optimizer, logits=logits) diff --git a/tensorflow/python/estimator/canned/dnn_testing_utils.py b/tensorflow/python/estimator/canned/dnn_testing_utils.py index 85b058caf3..44545c058c 100644 --- a/tensorflow/python/estimator/canned/dnn_testing_utils.py +++ b/tensorflow/python/estimator/canned/dnn_testing_utils.py @@ -53,7 +53,7 @@ from tensorflow.python.summary.writer import writer_cache from tensorflow.python.training import checkpoint_utils from tensorflow.python.training import gradient_descent from tensorflow.python.training import monitored_session -from tensorflow.python.training import optimizer +from tensorflow.python.training import optimizer as optimizer_lib from tensorflow.python.training import saver from tensorflow.python.training import session_run_hook from tensorflow.python.training import training_util @@ -134,7 +134,8 @@ def mock_head(testcase, hidden_units, logits_dimension, expected_logits): hidden_weights_names + hidden_biases_names + [LOGITS_WEIGHTS_NAME + '/part_0:0', LOGITS_BIASES_NAME + '/part_0:0']) - def _create_estimator_spec(features, mode, logits, labels, train_op_fn): + def _create_estimator_spec( + features, mode, logits, labels, train_op_fn=None, optimizer=None): del features, labels # Not used. trainable_vars = ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES) testcase.assertItemsEqual(expected_var_names, @@ -144,8 +145,12 @@ def mock_head(testcase, hidden_units, logits_dimension, expected_logits): expected_logits, logits, message='Failed for mode={}. '.format(mode)) with ops.control_dependencies([assert_logits]): if mode == model_fn.ModeKeys.TRAIN: + if train_op_fn is not None: + train_op = train_op_fn(loss) + elif optimizer is not None: + train_op = optimizer.minimize(loss, global_step=None) return model_fn.EstimatorSpec( - mode=mode, loss=loss, train_op=train_op_fn(loss)) + mode=mode, loss=loss, train_op=train_op) elif mode == model_fn.ModeKeys.EVAL: return model_fn.EstimatorSpec(mode=mode, loss=array_ops.identity(loss)) elif mode == model_fn.ModeKeys.PREDICT: @@ -203,8 +208,8 @@ def mock_optimizer(testcase, hidden_units, expected_loss=None): return control_flow_ops.no_op() optimizer_mock = test.mock.NonCallableMagicMock( - spec=optimizer.Optimizer, - wraps=optimizer.Optimizer(use_locking=False, name='my_optimizer')) + spec=optimizer_lib.Optimizer, + wraps=optimizer_lib.Optimizer(use_locking=False, name='my_optimizer')) optimizer_mock.minimize = test.mock.MagicMock(wraps=_minimize) return optimizer_mock diff --git a/tensorflow/python/estimator/canned/head.py b/tensorflow/python/estimator/canned/head.py index f68204a35e..c9635a9c27 100644 --- a/tensorflow/python/estimator/canned/head.py +++ b/tensorflow/python/estimator/canned/head.py @@ -44,6 +44,7 @@ from tensorflow.python.ops import weights_broadcast_ops from tensorflow.python.ops.losses import losses from tensorflow.python.saved_model import signature_constants from tensorflow.python.summary import summary +from tensorflow.python.training import training_util _DEFAULT_SERVING_KEY = signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY @@ -85,40 +86,39 @@ class _Head(object): ```python def _my_dnn_model_fn(features, labels, mode, params, config=None): # Optionally your callers can pass head to model_fn as a param. - head = tf.contrib.learn.regression_head(...) - input = tf.contrib.layers.input_from_feature_columns(features, ...) - last_hidden_layer_out = tf.contrib.layers.stack( - input, tf.contrib.layers.fully_connected, [1000, 500]) - logits = tf.contrib.layers.fully_connected( - last_hidden_layer_out, head.logits_dimension, activation_fn=None) - - def _train_op_fn(loss): - return optimizer.minimize(loss) + head = tf.contrib.estimator.regression_head(...) + inputs = tf.feature_column.input_layer(features, ...) + hidden_layer0 = tf.layers.dense( + inputs, units=1000, activation=tf.nn.relu) + hidden_layer1 = tf.layers.dense( + hidden_layer0, units=500, activation=tf.nn.relu) + logits = tf.layers.dense( + hidden_layer1, units=head.logits_dimension, activation=None) return head.create_estimator_spec( features=features, labels=labels, mode=mode, logits=logits, - train_op_fn=_train_op_fn) + optimizer=optimizer) ``` There are cases where computing and applying gradients can not be meaningfully - captured with train_op_fn we support (for example, with sync optimizer). In - such case, you can take the responsibility on your own. Here is a common - use case, + captured with optimizer or train_op_fn we support (for example, with sync + optimizer). In such case, you can take the responsibility on your own. Here is + a common use case, ```python estimator_spec = head.create_estimator_spec( features=features, labels=labels, mode=mode, logits=logits, - train_op_fn=tf.contrib.learn.no_op_train_fn) + train_op_fn=lambda _: tf.no_op()) if mode == model_fn.ModeKeys.TRAIN: optimizer = ... sync = tf.train.SyncReplicasOptimizer(opt=optimizer, ...) - update_op = tf.contrib.layers.optimize_loss(optimizer=sync, - loss=estimator_spec.loss, ...) + update_op = sync.minimize( + estimator_spec.loss, global_step=tf.get_global_step()) hooks = [sync.make_session_run_hook(is_chief)] ... update train_op and hooks in EstimatorSpec and return ``` @@ -172,10 +172,12 @@ class _Head(object): """ raise NotImplementedError('Calling an abstract method.') + # TODO(b/65403806): By default, collect regularization_losses from + # GraphKeys.REGULARIZATION_LOSSES collection. @abc.abstractmethod def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None, - regularization_losses=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None, regularization_losses=None): """Returns `EstimatorSpec` that a model_fn can return. Please note that, @@ -186,10 +188,14 @@ class _Head(object): mode: Estimator's `ModeKeys`. logits: logits `Tensor` to be used by the head. labels: Labels `Tensor`, or `dict` of same. + optimizer: `Optimizer` instance to optimize the loss in TRAIN mode. + Namely, sets `train_op = optimizer.minimize(loss, global_step)`, which + updates variables and increments `global_step`. train_op_fn: Function that takes a scalar loss `Tensor` and returns an op - to optimize the model with the loss. This is used in TRAIN mode and - must not be None. None is allowed in other modes. If you want to - optimize loss yourself you can pass `no_op_train_fn` and then use + to optimize the model with the loss in TRAIN mode. Used if `optimizer` + is `None`. Exactly one of `train_op_fn` and `optimizer` must be set in + TRAIN mode. None is allowed in other modes. If you want to optimize loss + yourself you can pass `lambda _: tf.no_op()` and then use EstimatorSpec.loss to compute and apply gradients. regularization_losses: A list of additional scalar losses to be added to the training loss, such as regularization losses. @@ -694,8 +700,8 @@ class _MultiClassHeadWithSoftmaxCrossEntropyLoss(_Head): processed_labels=label_ids) def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None, - regularization_losses=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None, regularization_losses=None): """Returns an `EstimatorSpec`. Args: @@ -706,8 +712,11 @@ class _MultiClassHeadWithSoftmaxCrossEntropyLoss(_Head): labels: Labels integer or string `Tensor` with shape matching `logits`, namely `[D0, D1, ... DN, 1]` or `[D0, D1, ... DN]`. `labels` is required argument when `mode` equals `TRAIN` or `EVAL`. + optimizer: `Optimizer` instance to optimize the loss in TRAIN mode. + Namely, sets `train_op = optimizer.minimize(loss, global_step)`, which + updates variables and increments `global_step`. train_op_fn: Function that takes a scalar loss `Tensor` and returns - `train_op`. Required in TRAIN mode. + `train_op`. Used if `optimizer` is `None`. regularization_losses: A list of additional scalar losses to be added to the training loss, such as regularization losses. These losses are usually expressed as a batch average, so for best results users need to @@ -717,7 +726,8 @@ class _MultiClassHeadWithSoftmaxCrossEntropyLoss(_Head): Returns: `EstimatorSpec`. Raises: - ValueError: If `train_op_fn` is `None` in TRAIN mode. + ValueError: If both `train_op_fn` and `optimizer` are `None` in TRAIN + mode, or if both are set. """ with ops.name_scope(self._name, 'head'): logits = _check_logits_final_dim(logits, self.logits_dimension) @@ -780,8 +790,16 @@ class _MultiClassHeadWithSoftmaxCrossEntropyLoss(_Head): regularization_loss=regularization_loss)) # Train. - if train_op_fn is None: - raise ValueError('train_op_fn cannot be None.') + if optimizer is not None: + if train_op_fn is not None: + raise ValueError('train_op_fn and optimizer cannot both be set.') + train_op = optimizer.minimize( + regularized_training_loss, + global_step=training_util.get_global_step()) + elif train_op_fn is not None: + train_op = train_op_fn(regularized_training_loss) + else: + raise ValueError('train_op_fn and optimizer cannot both be None.') # Only summarize mean_loss for SUM reduction to preserve backwards # compatibility. Otherwise skip it to avoid unnecessary computation. if self._loss_reduction == losses.Reduction.SUM: @@ -807,7 +825,7 @@ class _MultiClassHeadWithSoftmaxCrossEntropyLoss(_Head): mode=model_fn.ModeKeys.TRAIN, predictions=predictions, loss=regularized_training_loss, - train_op=train_op_fn(regularized_training_loss)) + train_op=train_op) def _binary_logistic_head_with_sigmoid_cross_entropy_loss( @@ -1039,8 +1057,8 @@ class _BinaryLogisticHeadWithSigmoidCrossEntropyLoss(_Head): processed_labels=labels) def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None, - regularization_losses=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None, regularization_losses=None): """Returns an `EstimatorSpec`. Args: @@ -1051,8 +1069,11 @@ class _BinaryLogisticHeadWithSigmoidCrossEntropyLoss(_Head): labels: Labels integer or string `Tensor` with shape matching `logits`, namely `[D0, D1, ... DN, 1]` or `[D0, D1, ... DN]`. `labels` is required argument when `mode` equals `TRAIN` or `EVAL`. + optimizer: `Optimizer` instance to optimize the loss in TRAIN mode. + Namely, sets `train_op = optimizer.minimize(loss, global_step)`, which + updates variables and increments `global_step`. train_op_fn: Function that takes a scalar loss `Tensor` and returns - `train_op`. Required in TRAIN mode. + `train_op`. Used if `optimizer` is `None`. regularization_losses: A list of additional scalar losses to be added to the training loss, such as regularization losses. These losses are usually expressed as a batch average, so for best results users need to @@ -1062,7 +1083,8 @@ class _BinaryLogisticHeadWithSigmoidCrossEntropyLoss(_Head): Returns: `EstimatorSpec`. Raises: - ValueError: If `train_op_fn` is `None` in TRAIN mode. + ValueError: If both `train_op_fn` and `optimizer` are `None` in TRAIN + mode, or if both are set. """ # Predict. with ops.name_scope(self._name, 'head'): @@ -1134,8 +1156,16 @@ class _BinaryLogisticHeadWithSigmoidCrossEntropyLoss(_Head): regularization_loss=regularization_loss)) # Train. - if train_op_fn is None: - raise ValueError('train_op_fn can not be None.') + if optimizer is not None: + if train_op_fn is not None: + raise ValueError('train_op_fn and optimizer cannot both be set.') + train_op = optimizer.minimize( + regularized_training_loss, + global_step=training_util.get_global_step()) + elif train_op_fn is not None: + train_op = train_op_fn(regularized_training_loss) + else: + raise ValueError('train_op_fn and optimizer cannot both be None.') # Only summarize mean_loss for SUM reduction to preserve backwards # compatibility. Otherwise skip it to avoid unnecessary computation. if self._loss_reduction == losses.Reduction.SUM: @@ -1160,7 +1190,7 @@ class _BinaryLogisticHeadWithSigmoidCrossEntropyLoss(_Head): mode=model_fn.ModeKeys.TRAIN, predictions=predictions, loss=regularized_training_loss, - train_op=train_op_fn(regularized_training_loss)) + train_op=train_op) def _regression_head_with_mean_squared_error_loss( @@ -1289,8 +1319,8 @@ class _RegressionHeadWithMeanSquaredErrorLoss(_Head): processed_labels=labels) def create_estimator_spec( - self, features, mode, logits, labels=None, train_op_fn=None, - regularization_losses=None): + self, features, mode, logits, labels=None, optimizer=None, + train_op_fn=None, regularization_losses=None): """Returns an `EstimatorSpec`. Args: @@ -1302,8 +1332,11 @@ class _RegressionHeadWithMeanSquaredErrorLoss(_Head): `[D0, D1, ... DN, logits_dimension]`. When `logits_dimension=1`, shape `[D0, D1, ... DN]` is also supported. `labels` is required argument when `mode` equals `TRAIN` or `EVAL`. + optimizer: `Optimizer` instance to optimize the loss in TRAIN mode. + Namely, sets `train_op = optimizer.minimize(loss, global_step)`, which + updates variables and increments `global_step`. train_op_fn: Function that takes a scalar loss `Tensor` and returns - `train_op`. Required in TRAIN mode. + `train_op`. Used if `optimizer` is `None`. regularization_losses: A list of additional scalar losses to be added to the training loss, such as regularization losses. These losses are usually expressed as a batch average, so for best results users need to @@ -1313,7 +1346,8 @@ class _RegressionHeadWithMeanSquaredErrorLoss(_Head): Returns: `EstimatorSpec`. Raises: - ValueError: If `train_op_fn` is `None` in TRAIN mode. + ValueError: If both `train_op_fn` and `optimizer` are `None` in TRAIN + mode, or if both are set. """ # Predict. with ops.name_scope(self._name, 'head'): @@ -1373,8 +1407,16 @@ class _RegressionHeadWithMeanSquaredErrorLoss(_Head): eval_metric_ops=eval_metric_ops) # Train. - if train_op_fn is None: - raise ValueError('train_op_fn can not be None.') + if optimizer is not None: + if train_op_fn is not None: + raise ValueError('train_op_fn and optimizer cannot both be set.') + train_op = optimizer.minimize( + regularized_training_loss, + global_step=training_util.get_global_step()) + elif train_op_fn is not None: + train_op = train_op_fn(regularized_training_loss) + else: + raise ValueError('train_op_fn and optimizer cannot both be None.') # Only summarize mean_loss for SUM reduction to preserve backwards # compatibility. Otherwise skip it to avoid unnecessary computation. if self._loss_reduction == losses.Reduction.SUM: @@ -1399,7 +1441,7 @@ class _RegressionHeadWithMeanSquaredErrorLoss(_Head): mode=model_fn.ModeKeys.TRAIN, predictions=predictions, loss=regularized_training_loss, - train_op=train_op_fn(regularized_training_loss)) + train_op=train_op) def _assert_range(labels, n_classes, message=None): diff --git a/tensorflow/python/estimator/canned/head_test.py b/tensorflow/python/estimator/canned/head_test.py index b5d35c9b45..fe6ee07529 100644 --- a/tensorflow/python/estimator/canned/head_test.py +++ b/tensorflow/python/estimator/canned/head_test.py @@ -842,6 +842,41 @@ class MultiClassHeadWithSoftmaxCrossEntropyLoss(test.TestCase): metric_keys.MetricKeys.LOSS_MEAN: expected_loss / 2, }, summary_str, tol) + def test_train_with_optimizer(self): + n_classes = 3 + head = head_lib._multi_class_head_with_softmax_cross_entropy_loss(n_classes) + + logits = np.array(((10, 0, 0), (0, 10, 0),), dtype=np.float32) + labels = np.array(((1,), (1,)), dtype=np.int64) + features = {'x': np.array(((42,),), dtype=np.int32)} + expected_train_result = 'my_train_op' + + class _Optimizer(object): + + def minimize(self, loss, global_step): + del global_step + return string_ops.string_join( + [constant_op.constant(expected_train_result), + string_ops.as_string(loss, precision=2)]) + + # loss = sum(cross_entropy(labels, logits)) = sum(10, 0) = 10. + expected_loss = 10. + spec = head.create_estimator_spec( + features=features, + mode=model_fn.ModeKeys.TRAIN, + logits=logits, + labels=labels, + optimizer=_Optimizer()) + + tol = 1e-2 + with self.test_session() as sess: + _initialize_variables(self, spec.scaffold) + loss, train_result = sess.run((spec.loss, spec.train_op)) + self.assertAllClose(expected_loss, loss, rtol=tol, atol=tol) + self.assertEqual( + six.b('{0:s}{1:.2f}'.format(expected_train_result, expected_loss)), + train_result) + def test_train_summaries_with_head_name(self): n_classes = 3 head = head_lib._multi_class_head_with_softmax_cross_entropy_loss( @@ -1942,6 +1977,39 @@ class BinaryLogisticHeadWithSigmoidCrossEntropyLossTest(test.TestCase): metric_keys.MetricKeys.LOSS_MEAN: 20.5, }, summary_str) + def test_train_with_optimizer(self): + head = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss() + + logits = np.array(((45,), (-41,),), dtype=np.float32) + labels = np.array(((1,), (1,),), dtype=np.float64) + expected_train_result = b'my_train_op' + features = {'x': np.array(((42,),), dtype=np.float32)} + # loss = sum(cross_entropy(labels, logits)) = sum(0, 41) = 41 + expected_loss = 41. + + class _Optimizer(object): + + def minimize(self, loss, global_step): + del global_step + with ops.control_dependencies((check_ops.assert_equal( + math_ops.to_float(expected_loss), math_ops.to_float(loss), + name='assert_loss'),)): + return constant_op.constant(expected_train_result) + + # Create estimator spec. + spec = head.create_estimator_spec( + features=features, + mode=model_fn.ModeKeys.TRAIN, + logits=logits, + labels=labels, + optimizer=_Optimizer()) + + with self.test_session() as sess: + _initialize_variables(self, spec.scaffold) + loss, train_result = sess.run((spec.loss, spec.train_op)) + self.assertAllClose(expected_loss, loss) + self.assertEqual(expected_train_result, train_result) + def test_train_summaries_with_head_name(self): head = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss( name='some_binary_head') @@ -3076,6 +3144,40 @@ class RegressionHeadWithMeanSquaredErrorLossTest(test.TestCase): metric_keys.MetricKeys.LOSS_MEAN: 6.5, }, summary_str) + def test_train_with_optimizer(self): + head = head_lib._regression_head_with_mean_squared_error_loss() + self.assertEqual(1, head.logits_dimension) + + # Create estimator spec. + logits = np.array(((45,), (41,),), dtype=np.float32) + labels = np.array(((43.,), (44.,),), dtype=np.float64) + expected_train_result = b'my_train_op' + features = {'x': np.array(((42.,),), dtype=np.float32)} + # loss = (43-45)^2 + (44-41)^2 = 4 + 9 = 13 + expected_loss = 13 + + class _Optimizer(object): + + def minimize(self, loss, global_step): + del global_step + with ops.control_dependencies((check_ops.assert_equal( + math_ops.to_float(expected_loss), math_ops.to_float(loss), + name='assert_loss'),)): + return constant_op.constant(expected_train_result) + + spec = head.create_estimator_spec( + features=features, + mode=model_fn.ModeKeys.TRAIN, + logits=logits, + labels=labels, + optimizer=_Optimizer()) + + with self.test_session() as sess: + _initialize_variables(self, spec.scaffold) + loss, train_result = sess.run((spec.loss, spec.train_op)) + self.assertAllClose(expected_loss, loss) + self.assertEqual(expected_train_result, train_result) + def test_train_summaries_with_head_name(self): head = head_lib._regression_head_with_mean_squared_error_loss( name='some_regression_head') diff --git a/tensorflow/python/estimator/canned/linear.py b/tensorflow/python/estimator/canned/linear.py index a2f24ef270..e7ec417991 100644 --- a/tensorflow/python/estimator/canned/linear.py +++ b/tensorflow/python/estimator/canned/linear.py @@ -33,7 +33,6 @@ from tensorflow.python.ops import variable_scope from tensorflow.python.ops.losses import losses from tensorflow.python.summary import summary from tensorflow.python.training import ftrl -from tensorflow.python.training import training_util from tensorflow.python.util.tf_export import tf_export @@ -157,17 +156,11 @@ def _linear_model_fn(features, labels, mode, head, feature_columns, optimizer, units=head.logits_dimension, feature_columns=feature_columns) logits = logit_fn(features=features) - def _train_op_fn(loss): - """Returns the op to optimize the loss.""" - return optimizer.minimize( - loss, - global_step=training_util.get_global_step()) - return head.create_estimator_spec( features=features, mode=mode, labels=labels, - train_op_fn=_train_op_fn, + optimizer=optimizer, logits=logits) -- GitLab From 48b0fb7a524425d57547dc23093d869538b888db Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Thu, 22 Mar 2018 14:09:59 -0700 Subject: [PATCH 096/906] Fetch C shapes for ops created by import_graph_def with C API enabled. If _USE_C_API = True, this change makes us always fetch shapes using the C API after calling TF_ImportGraphDef, even if _USE_C_SHAPES = False. This is necessary to preserve the shapes specified by the "_output_shapes" attr on imported NodeDefs (note that this attr isn't present on the NodeDefs of the imported nodes, so there's no other way to recover this information after calling TF_ImportGraphDef). PiperOrigin-RevId: 190122991 --- tensorflow/python/framework/ops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index de222e1932..93edaa0cf0 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -3455,12 +3455,12 @@ class Graph(object): ] for op in new_ops: - # The Python shape inference code does not support imported functions. It - # also needs access to op.inputs, which is why we call it here. + # Operations created by the C API always retrieve shapes from the C API so + # we preserve the shapes of ops created in import_graph_def (from the + # "_output_shapes" attr of the imported NodeDef). # TODO(b/74620627): move this back to _create_op_helper once _USE_C_SHAPES # is removed. - if not self._is_function(op.type) or _USE_C_SHAPES: - set_shapes_for_outputs(op) + _set_shapes_for_outputs_c_api(op) new_control_inputs = self._control_dependencies_for_inputs(op.inputs) # pylint: disable=protected-access op._add_control_inputs(new_control_inputs) -- GitLab From e3468b56d323783fdfb79fa2d6c24effc58bcaa9 Mon Sep 17 00:00:00 2001 From: Brian Patton Date: Thu, 22 Mar 2018 14:11:08 -0700 Subject: [PATCH 097/906] Adds float64 support for Conv2d, Conv2dBackpropInput, and Conv2dBackpropFilter PiperOrigin-RevId: 190123191 --- .../core/kernels/conv_grad_filter_ops.cc | 7 + .../core/kernels/conv_grad_input_ops.cc | 7 + tensorflow/core/kernels/conv_ops.cc | 7 +- tensorflow/core/kernels/conv_ops_gpu_2.cu.cc | 3 + tensorflow/core/kernels/conv_ops_gpu_3.cu.cc | 3 + tensorflow/core/kernels/depthwise_conv_op.cc | 4 +- .../core/kernels/eigen_spatial_convolutions.h | 182 ++++++++++++++++++ tensorflow/core/ops/nn_ops.cc | 6 +- .../python/kernel_tests/conv_ops_test.py | 4 +- 9 files changed, 215 insertions(+), 8 deletions(-) diff --git a/tensorflow/core/kernels/conv_grad_filter_ops.cc b/tensorflow/core/kernels/conv_grad_filter_ops.cc index e6ae595291..66ee474ca3 100644 --- a/tensorflow/core/kernels/conv_grad_filter_ops.cc +++ b/tensorflow/core/kernels/conv_grad_filter_ops.cc @@ -520,6 +520,7 @@ class Conv2DCustomBackpropFilterOp : public OpKernel { TF_CALL_half(REGISTER_CPU_KERNELS); TF_CALL_float(REGISTER_CPU_KERNELS); +TF_CALL_double(REGISTER_CPU_KERNELS); #undef REGISTER_CPU_KERNELS // GPU definitions. @@ -1017,11 +1018,17 @@ namespace functor { typename TTypes::Tensor out, TensorFormat data_format); \ extern template struct PadInput; +DECLARE_GPU_SPEC(double); DECLARE_GPU_SPEC(float); DECLARE_GPU_SPEC(Eigen::half); #undef DECLARE_GPU_SPEC } // namespace functor +REGISTER_KERNEL_BUILDER(Name("Conv2DBackpropFilter") + .Device(DEVICE_GPU) + .TypeConstraint("T") + .HostMemory("filter_sizes"), + Conv2DSlowBackpropFilterOp); REGISTER_KERNEL_BUILDER(Name("Conv2DBackpropFilter") .Device(DEVICE_GPU) .TypeConstraint("T") diff --git a/tensorflow/core/kernels/conv_grad_input_ops.cc b/tensorflow/core/kernels/conv_grad_input_ops.cc index 15c55e4d99..71ea0d5d72 100644 --- a/tensorflow/core/kernels/conv_grad_input_ops.cc +++ b/tensorflow/core/kernels/conv_grad_input_ops.cc @@ -592,6 +592,7 @@ class Conv2DCustomBackpropInputOp : public OpKernel { TF_CALL_half(REGISTER_CPU_KERNELS); TF_CALL_float(REGISTER_CPU_KERNELS); +TF_CALL_double(REGISTER_CPU_KERNELS); #undef REGISTER_CPU_KERNELS // GPU definitions. @@ -1090,11 +1091,17 @@ namespace functor { typename TTypes::Tensor out, TensorFormat data_format); \ extern template struct PadInput; +DECLARE_GPU_SPEC(double); DECLARE_GPU_SPEC(float); DECLARE_GPU_SPEC(Eigen::half); #undef DECLARE_GPU_SPEC } // namespace functor +REGISTER_KERNEL_BUILDER(Name("Conv2DBackpropInput") + .Device(DEVICE_GPU) + .TypeConstraint("T") + .HostMemory("input_sizes"), + Conv2DSlowBackpropInputOp); REGISTER_KERNEL_BUILDER(Name("Conv2DBackpropInput") .Device(DEVICE_GPU) .TypeConstraint("T") diff --git a/tensorflow/core/kernels/conv_ops.cc b/tensorflow/core/kernels/conv_ops.cc index 47f6907c04..88843e4da7 100644 --- a/tensorflow/core/kernels/conv_ops.cc +++ b/tensorflow/core/kernels/conv_ops.cc @@ -446,10 +446,11 @@ class Conv2DOp : public BinaryOp { #if !defined(USE_GEMM_FOR_CONV) TF_CALL_half(REGISTER_CPU); TF_CALL_float(REGISTER_CPU); +TF_CALL_double(REGISTER_CPU); #endif // USE_GEMM_FOR_CONV // To be used inside depthwise_conv_op.cc. -template class LaunchConv2DOp; +template struct LaunchConv2DOp; #if GOOGLE_CUDA int64 GetCudnnWorkspaceLimit(const string& envvar_in_mb, @@ -810,6 +811,7 @@ namespace functor { typename TTypes::Tensor out, TensorFormat data_format); \ extern template struct PadInput +DECLARE_GPU_SPEC(double); DECLARE_GPU_SPEC(float); DECLARE_GPU_SPEC(Eigen::half); #undef DECLARE_GPU_SPEC @@ -822,6 +824,9 @@ REGISTER_KERNEL_BUILDER( REGISTER_KERNEL_BUILDER( Name("Conv2D").Device(DEVICE_GPU).TypeConstraint("T"), Conv2DOp); +REGISTER_KERNEL_BUILDER( + Name("Conv2D").Device(DEVICE_GPU).TypeConstraint("T"), + Conv2DOp); // To be used inside depthwise_conv_op.cc. template class LaunchConv2DOp; diff --git a/tensorflow/core/kernels/conv_ops_gpu_2.cu.cc b/tensorflow/core/kernels/conv_ops_gpu_2.cu.cc index b5dd26a9e4..52859af950 100644 --- a/tensorflow/core/kernels/conv_ops_gpu_2.cu.cc +++ b/tensorflow/core/kernels/conv_ops_gpu_2.cu.cc @@ -25,6 +25,9 @@ limitations under the License. namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; +template struct functor::InflatePadAndShuffle; +template struct functor::InflatePadAndShuffle; template struct functor::InflatePadAndShuffle; template struct functor::InflatePadAndShuffle; diff --git a/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc b/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc index a376534bad..2503b475dc 100644 --- a/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc +++ b/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc @@ -1039,9 +1039,11 @@ template struct functor::SwapDimension0And2InTensor3; // For 2d ops. +template struct functor::TransformFilter; template struct functor::TransformFilter; template struct functor::TransformFilter; +template struct functor::ReverseTransformFilter; template struct functor::ReverseTransformFilter; template struct functor::ReverseTransformFilter; @@ -1054,6 +1056,7 @@ template struct functor::NCHWToNHWC; template struct functor::NCHWToNHWC; template struct functor::PadInput; +template struct functor::PadInput; template struct functor::PadInput; template struct functor::PadInput; diff --git a/tensorflow/core/kernels/depthwise_conv_op.cc b/tensorflow/core/kernels/depthwise_conv_op.cc index c060b2e14d..6dedb1a61e 100644 --- a/tensorflow/core/kernels/depthwise_conv_op.cc +++ b/tensorflow/core/kernels/depthwise_conv_op.cc @@ -241,7 +241,7 @@ struct LaunchDepthwiseConvOp { }; // Extern template instantiated in conv_ops.cc. -extern template class LaunchConv2DOp; +extern template struct LaunchConv2DOp; #if GOOGLE_CUDA @@ -251,7 +251,7 @@ extern template struct LaunchDepthwiseConvOp; extern template struct LaunchDepthwiseConvOp; // Extern template instantiated in conv_ops.cc. -extern template class LaunchConv2DOp; +extern template struct LaunchConv2DOp; #endif diff --git a/tensorflow/core/kernels/eigen_spatial_convolutions.h b/tensorflow/core/kernels/eigen_spatial_convolutions.h index 1acbe3a658..a4dff4b91c 100644 --- a/tensorflow/core/kernels/eigen_spatial_convolutions.h +++ b/tensorflow/core/kernels/eigen_spatial_convolutions.h @@ -797,6 +797,188 @@ struct gemm_pack_rhs< } }; +// Template specialization for packet_size = 2. We must special-case packet +// blocks with nr > packet_size, e.g. PacketBlock. +template +struct gemm_pack_rhs< + Scalar, Index, + TensorContractionSubMapper< + Scalar, Index, Rhs, + TensorEvaluator< + const TensorReshapingOp< + NewDimension, const TensorImagePatchOp >, + Device>, + nocontract_t, contract_t, 2, inner_dim_contiguous, inner_dim_reordered, + Alignment>, + nr, ColMajor, false, false> { + typedef TensorContractionSubMapper< + Scalar, Index, Rhs, + TensorEvaluator< + const TensorReshapingOp< + NewDimension, const TensorImagePatchOp >, + Device>, + nocontract_t, contract_t, 2, inner_dim_contiguous, inner_dim_reordered, + Alignment> + SubMapper; + typedef SubMapper DataMapper; + + EIGEN_DEVICE_FUNC + static inline Index ceil_div(Index a, Index b) { return (a + b - 1) / b; } + + EIGEN_DEVICE_FUNC + EIGEN_DONT_INLINE void operator()(Scalar* block, const DataMapper& rhs, + Index depth, Index cols, Index stride = 0, + Index offset = 0) const { + eigen_assert(stride == 0); + eigen_assert(offset == 0); + + EIGEN_STATIC_ASSERT((nr == 4), YOU_MADE_A_PROGRAMMING_MISTAKE); + typedef typename packet_traits::type Packet; + + const int packet_size = 2; + const Index packet_cols4 = (cols / 4) * 4; + const Index peeled_k = (depth / packet_size) * packet_size; + const bool non_standard_patches = rhs.nonStandardPatches(); + + for (Index j2 = 0; j2 < packet_cols4; j2 += 4) { + const SubMapper dm0 = rhs.getLinearMapper(0, j2 + 0); + const SubMapper dm1 = rhs.getLinearMapper(0, j2 + 1); + const SubMapper dm2 = rhs.getLinearMapper(0, j2 + 2); + const SubMapper dm3 = rhs.getLinearMapper(0, j2 + 3); + + Index k = 0; + if (!non_standard_patches) { + const Index patch_depth = rhs.patchDepth(); + if ((patch_depth % packet_size) == 0) { + const Index patch_cols = rhs.patchCols(); + const Index patch_rows = rhs.patchRows(); + + const Index startCol = rhs.colOffset(); + const Index max_cols = std::min( + ceil_div(peeled_k, patch_rows * patch_depth) + startCol, + patch_cols); + + for (Index c = startCol; c < max_cols; ++c) { + eigen_assert(k < peeled_k); + const Index startRow = (c == startCol) ? rhs.rowOffset() : 0; + const Index max_rows = std::min( + ceil_div(peeled_k - c * patch_rows * patch_depth, patch_depth) + + startRow, + patch_rows); + + const bool pad_col0 = dm0.padCol(c); + const bool pad_col1 = dm1.padCol(c); + const bool pad_col2 = dm2.padCol(c); + const bool pad_col3 = dm3.padCol(c); + for (Index r = startRow; r < max_rows; ++r) { + eigen_assert(k < peeled_k); + const bool pad0 = pad_col0 || dm0.padRow(r); + const bool pad1 = pad_col1 || dm1.padRow(r); + const bool pad2 = pad_col2 || dm2.padRow(r); + const bool pad3 = pad_col3 || dm3.padRow(r); + + const Index idx0 = dm0.baseIndex(r, c); + const Index idx1 = dm1.baseIndex(r, c); + const Index idx2 = dm2.baseIndex(r, c); + const Index idx3 = dm3.baseIndex(r, c); + + const Index startDepth = + ((c == startCol) && (r == startRow)) ? rhs.depthOffset() : 0; + const Index max_depth = + std::min(peeled_k - c * patch_rows * patch_depth - + r * patch_depth + startDepth, + patch_depth); + eigen_assert((max_depth - startDepth) % packet_size == 0); + for (Index d = startDepth; d < max_depth; d += packet_size) { + eigen_assert(k < peeled_k); + PacketBlock kernel0; + PacketBlock kernel1; + kernel0.packet[0] = pad0 ? pset1(Scalar(0)) + : rhs.packetNoPadding(d, idx0); + kernel0.packet[1] = pad1 ? pset1(Scalar(0)) + : rhs.packetNoPadding(d, idx1); + kernel1.packet[0] = pad2 ? pset1(Scalar(0)) + : rhs.packetNoPadding(d, idx2); + kernel1.packet[1] = pad3 ? pset1(Scalar(0)) + : rhs.packetNoPadding(d, idx3); + ptranspose(kernel0); + ptranspose(kernel1); + pstoreu(block + 0 * packet_size, kernel0.packet[0]); + pstoreu(block + 1 * packet_size, kernel1.packet[0]); + pstoreu(block + 2 * packet_size, kernel0.packet[1]); + pstoreu(block + 3 * packet_size, kernel1.packet[1]); + block += 4 * packet_size; + k += packet_size; + } + } + } + + for (; k < peeled_k; k += packet_size) { + PacketBlock kernel0; + PacketBlock kernel1; + kernel0.packet[0] = dm0.loadPacketFast(k); + kernel0.packet[1] = dm1.loadPacketFast(k); + kernel1.packet[0] = dm2.loadPacketFast(k); + kernel1.packet[1] = dm3.loadPacketFast(k); + ptranspose(kernel0); + ptranspose(kernel1); + pstoreu(block + 0 * packet_size, kernel0.packet[0]); + pstoreu(block + 1 * packet_size, kernel1.packet[0]); + pstoreu(block + 2 * packet_size, kernel0.packet[1]); + pstoreu(block + 3 * packet_size, kernel1.packet[1]); + block += 4 * packet_size; + } + } else { + for (; k < peeled_k; k += packet_size) { + PacketBlock kernel0; + PacketBlock kernel1; + kernel0.packet[0] = dm0.loadPacketStandard(k); + kernel0.packet[1] = dm1.loadPacketStandard(k); + kernel1.packet[0] = dm2.loadPacketStandard(k); + kernel1.packet[1] = dm3.loadPacketStandard(k); + ptranspose(kernel0); + ptranspose(kernel1); + pstoreu(block + 0 * packet_size, kernel0.packet[0]); + pstoreu(block + 1 * packet_size, kernel1.packet[0]); + pstoreu(block + 2 * packet_size, kernel0.packet[1]); + pstoreu(block + 3 * packet_size, kernel1.packet[1]); + block += 4 * packet_size; + } + } + } + if (!rhs.nonStandardPatches()) { + for (; k < depth; k++) { + block[0] = dm0.loadCoeffStandard(k); + block[1] = dm1.loadCoeffStandard(k); + block[2] = dm2.loadCoeffStandard(k); + block[3] = dm3.loadCoeffStandard(k); + block += 4; + } + } else { + for (; k < depth; k++) { + block[0] = dm0(k); + block[1] = dm1(k); + block[2] = dm2(k); + block[3] = dm3(k); + block += 4; + } + } + } + + // copy the remaining columns one at a time (nr==1) + for (Index j2 = packet_cols4; j2 < cols; ++j2) { + const SubMapper dm0 = rhs.getLinearMapper(0, j2); + for (Index k = 0; k < depth; k++) { + *block = dm0(k); + block += 1; + } + } + } +}; + // Special case for non-vectorized types such as float16. template Date: Thu, 22 Mar 2018 14:14:33 -0700 Subject: [PATCH 098/906] Update tf.keras preprocessing to Keras 2.1.5 API PiperOrigin-RevId: 190123773 --- .../keras/_impl/keras/preprocessing/image.py | 211 +++++++++++----- .../_impl/keras/preprocessing/image_test.py | 151 +++++++++++- .../_impl/keras/preprocessing/sequence.py | 232 ++++++++++++++---- .../keras/preprocessing/sequence_test.py | 82 ++++++- .../keras/_impl/keras/preprocessing/text.py | 31 ++- .../_impl/keras/preprocessing/text_test.py | 42 +++- .../keras/preprocessing/image/__init__.py | 1 + .../keras/preprocessing/sequence/__init__.py | 1 + .../keras/preprocessing/text/__init__.py | 1 + ...processing.image.-directory-iterator.pbtxt | 2 +- ...ocessing.image.-image-data-generator.pbtxt | 6 +- ...ocessing.image.-numpy-array-iterator.pbtxt | 2 +- ...tensorflow.keras.preprocessing.image.pbtxt | 4 + ...ssing.sequence.-timeseries-generator.pbtxt | 14 ++ ...sorflow.keras.preprocessing.sequence.pbtxt | 4 + .../tensorflow.keras.preprocessing.text.pbtxt | 4 + 16 files changed, 665 insertions(+), 123 deletions(-) create mode 100644 tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.-timeseries-generator.pbtxt diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/image.py b/tensorflow/python/keras/_impl/keras/preprocessing/image.py index d12f108639..6299445c34 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/image.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/image.py @@ -43,6 +43,7 @@ except ImportError: try: + from PIL import ImageEnhance from PIL import Image as pil_image except ImportError: pil_image = None @@ -227,6 +228,32 @@ def random_channel_shift(x, intensity, channel_axis=0): return x +@tf_export('keras.preprocessing.image.random_brightness') +def random_brightness(x, brightness_range): + """Performs a random adjustment of brightness of a Numpy image tensor. + + Arguments: + x: Input tensor. Must be 3D. + brightness_range: Tuple of floats; range to pick a brightness value from. + + Returns: + Brightness adjusted Numpy image tensor. + + Raises: + ValueError: if `brightness_range` isn't a tuple. + """ + if len(brightness_range) != 2: + raise ValueError('`brightness_range should be tuple or list of two floats. ' + 'Received arg: ', brightness_range) + + x = array_to_img(x) + x = ImageEnhance.Brightness(x) + u = np.random.uniform(brightness_range[0], brightness_range[1]) + x = x.enhance(u) + x = img_to_array(x) + return x + + def transform_matrix_offset_center(matrix, x, y): o_x = float(x) / 2 + 0.5 o_y = float(y) / 2 + 0.5 @@ -265,7 +292,7 @@ def apply_transform(x, x_channel, final_affine_matrix, final_offset, - order=0, + order=1, mode=fill_mode, cval=cval) for x_channel in x ] @@ -436,6 +463,7 @@ class ImageDataGenerator(object): rotation_range: degrees (0 to 180). width_shift_range: fraction of total width, if < 1, or pixels if >= 1. height_shift_range: fraction of total height, if < 1, or pixels if >= 1. + brightness_range: the range of brightness to apply shear_range: shear intensity (shear angle in degrees). zoom_range: amount of zoom. if scalar z, zoom will be randomly picked in the range [1-z, 1+z]. A sequence of two can be passed instead @@ -469,6 +497,8 @@ class ImageDataGenerator(object): It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". + validation_split: fraction of images reserved for validation (strictly + between 0 and 1). """ def __init__(self, @@ -481,6 +511,7 @@ class ImageDataGenerator(object): rotation_range=0., width_shift_range=0., height_shift_range=0., + brightness_range=None, shear_range=0., zoom_range=0., channel_shift_range=0., @@ -490,7 +521,8 @@ class ImageDataGenerator(object): vertical_flip=False, rescale=None, preprocessing_function=None, - data_format=None): + data_format=None, + validation_split=0.0): if data_format is None: data_format = K.image_data_format() self.featurewise_center = featurewise_center @@ -502,6 +534,7 @@ class ImageDataGenerator(object): self.rotation_range = rotation_range self.width_shift_range = width_shift_range self.height_shift_range = height_shift_range + self.brightness_range = brightness_range self.shear_range = shear_range self.zoom_range = zoom_range self.channel_shift_range = channel_shift_range @@ -526,6 +559,10 @@ class ImageDataGenerator(object): self.channel_axis = 3 self.row_axis = 1 self.col_axis = 2 + if validation_split and not 0 < validation_split < 1: + raise ValueError('`validation_split` must be strictly between 0 and 1. ' + 'Received arg: ', validation_split) + self.validation_split = validation_split self.mean = None self.std = None @@ -574,7 +611,8 @@ class ImageDataGenerator(object): seed=None, save_to_dir=None, save_prefix='', - save_format='png'): + save_format='png', + subset=None): return NumpyArrayIterator( x, y, @@ -585,7 +623,8 @@ class ImageDataGenerator(object): data_format=self.data_format, save_to_dir=save_to_dir, save_prefix=save_prefix, - save_format=save_format) + save_format=save_format, + subset=subset) def flow_from_directory(self, directory, @@ -600,6 +639,7 @@ class ImageDataGenerator(object): save_prefix='', save_format='png', follow_links=False, + subset=None, interpolation='nearest'): return DirectoryIterator( directory, @@ -616,6 +656,7 @@ class ImageDataGenerator(object): save_prefix=save_prefix, save_format=save_format, follow_links=follow_links, + subset=subset, interpolation=interpolation) def standardize(self, x): @@ -628,7 +669,7 @@ class ImageDataGenerator(object): The inputs, normalized. """ if self.preprocessing_function: - x = self.preprocessing_function(x) + x = self.image_data_generator.preprocessing_function(x) if self.rescale: x *= self.rescale if self.samplewise_center: @@ -762,6 +803,9 @@ class ImageDataGenerator(object): if np.random.random() < 0.5: x = flip_axis(x, img_row_axis) + if self.brightness_range is not None: + x = random_brightness(x, self.brightness_range) + return x def fit(self, x, augment=False, rounds=1, seed=None): @@ -828,12 +872,10 @@ class ImageDataGenerator(object): raise ImportError('Scipy is required for zca_whitening.') flat_x = np.reshape(x, (x.shape[0], x.shape[1] * x.shape[2] * x.shape[3])) - num_examples = flat_x.shape[0] - _, s, vt = linalg.svd(flat_x / np.sqrt(num_examples)) - s_expand = np.hstack( - (s, np.zeros(vt.shape[0] - num_examples, dtype=flat_x.dtype))) - self.principal_components = ( - vt.T / np.sqrt(s_expand**2 + self.zca_epsilon)).dot(vt) + sigma = np.dot(flat_x.T, flat_x) / flat_x.shape[0] + u, s, _ = linalg.svd(sigma) + s_inv = 1. / np.sqrt(s[np.newaxis] + self.zca_epsilon) + self.principal_components = (u * s_inv).dot(u.T) @tf_export('keras.preprocessing.image.Iterator') @@ -947,6 +989,8 @@ class NumpyArrayIterator(Iterator): images (if `save_to_dir` is set). save_format: Format to use for saving sample images (if `save_to_dir` is set). + subset: Subset of data (`"training"` or `"validation"`) if + validation_split is set in ImageDataGenerator. """ def __init__(self, @@ -959,17 +1003,29 @@ class NumpyArrayIterator(Iterator): data_format=None, save_to_dir=None, save_prefix='', - save_format='png'): + save_format='png', + subset=None): if y is not None and len(x) != len(y): - raise ValueError('X (images tensor) and y (labels) ' + raise ValueError('`x` (images tensor) and `y` (labels) ' 'should have the same length. ' - 'Found: X.shape = %s, y.shape = %s' % + 'Found: x.shape = %s, y.shape = %s' % (np.asarray(x).shape, np.asarray(y).shape)) - + if subset is not None: + if subset not in {'training', 'validation'}: + raise ValueError('Invalid subset name:', subset, + '; expected "training" or "validation".') + split_idx = int(len(x) * image_data_generator.validation_split) + if subset == 'validation': + x = x[:split_idx] + if y is not None: + y = y[:split_idx] + else: + x = x[split_idx:] + if y is not None: + y = y[split_idx:] if data_format is None: data_format = K.image_data_format() self.x = np.asarray(x, dtype=K.floatx()) - if self.x.ndim != 4: raise ValueError('Input data in `NumpyArrayIterator` ' 'should have rank 4. You passed an array ' @@ -1032,8 +1088,7 @@ class NumpyArrayIterator(Iterator): return self._get_batches_of_transformed_samples(index_array) -def _count_valid_files_in_directory(directory, white_list_formats, - follow_links): +def _iter_valid_files(directory, white_list_formats, follow_links): """Count files with extension in `white_list_formats` contained in directory. Arguments: @@ -1043,29 +1098,54 @@ def _count_valid_files_in_directory(directory, white_list_formats, the files to be counted. follow_links: boolean. - Returns: - the count of files with extension in `white_list_formats` contained in - the directory. + Yields: + tuple of (root, filename) with extension in `white_list_formats`. """ def _recursive_list(subpath): return sorted( - os.walk(subpath, followlinks=follow_links), key=lambda tpl: tpl[0]) + os.walk(subpath, followlinks=follow_links), key=lambda x: x[0]) - samples = 0 - for _, _, files in _recursive_list(directory): - for fname in files: - is_valid = False + for root, _, files in _recursive_list(directory): + for fname in sorted(files): for extension in white_list_formats: + if fname.lower().endswith('.tiff'): + logging.warning( + 'Using \'.tiff\' files with multiple bands will cause ' + 'distortion. Please verify your output.') if fname.lower().endswith('.' + extension): - is_valid = True - break - if is_valid: - samples += 1 - return samples + yield root, fname -def _list_valid_filenames_in_directory(directory, white_list_formats, +def _count_valid_files_in_directory(directory, white_list_formats, split, + follow_links): + """Count files with extension in `white_list_formats` contained in directory. + + Arguments: + directory: absolute path to the directory + containing files to be counted + white_list_formats: set of strings containing allowed extensions for + the files to be counted. + split: tuple of floats (e.g. `(0.2, 0.6)`) to only take into + account a certain fraction of files in each directory. + E.g.: `segment=(0.6, 1.0)` would only account for last 40 percent + of images in each directory. + follow_links: boolean. + + Returns: + the count of files with extension in `white_list_formats` contained in + the directory. + """ + num_files = len( + list(_iter_valid_files(directory, white_list_formats, follow_links))) + if split: + start, stop = int(split[0] * num_files), int(split[1] * num_files) + else: + start, stop = 0, num_files + return stop - start + + +def _list_valid_filenames_in_directory(directory, white_list_formats, split, class_indices, follow_links): """List paths of files in `subdir` with extensions in `white_list_formats`. @@ -1075,6 +1155,10 @@ def _list_valid_filenames_in_directory(directory, white_list_formats, `class_indices`. white_list_formats: set of strings containing allowed extensions for the files to be counted. + split: tuple of floats (e.g. `(0.2, 0.6)`) to only take into + account a certain fraction of files in each directory. + E.g.: `segment=(0.6, 1.0)` would only account for last 40 percent + of images in each directory. class_indices: dictionary mapping a class name to its index. follow_links: boolean. @@ -1084,27 +1168,26 @@ def _list_valid_filenames_in_directory(directory, white_list_formats, `directory`'s parent (e.g., if `directory` is "dataset/class1", the filenames will be ["class1/file1.jpg", "class1/file2.jpg", ...]). """ - - def _recursive_list(subpath): - return sorted( - os.walk(subpath, followlinks=follow_links), key=lambda tpl: tpl[0]) + dirname = os.path.basename(directory) + if split: + num_files = len( + list(_iter_valid_files(directory, white_list_formats, follow_links))) + start, stop = int(split[0] * num_files), int(split[1] * num_files) + valid_files = list( + _iter_valid_files(directory, white_list_formats, + follow_links))[start:stop] + else: + valid_files = _iter_valid_files(directory, white_list_formats, follow_links) classes = [] filenames = [] - subdir = os.path.basename(directory) - basedir = os.path.dirname(directory) - for root, _, files in _recursive_list(directory): - for fname in sorted(files): - is_valid = False - for extension in white_list_formats: - if fname.lower().endswith('.' + extension): - is_valid = True - break - if is_valid: - classes.append(class_indices[subdir]) - # add filename relative to directory - absolute_path = os.path.join(root, fname) - filenames.append(os.path.relpath(absolute_path, basedir)) + for root, fname in valid_files: + classes.append(class_indices[dirname]) + absolute_path = os.path.join(root, fname) + relative_path = os.path.join(dirname, + os.path.relpath(absolute_path, directory)) + filenames.append(relative_path) + return classes, filenames @@ -1144,6 +1227,8 @@ class DirectoryIterator(Iterator): images (if `save_to_dir` is set). save_format: Format to use for saving sample images (if `save_to_dir` is set). + subset: Subset of data (`"training"` or `"validation"`) if + validation_split is set in ImageDataGenerator. interpolation: Interpolation method used to resample the image if the target size is different from that of the loaded image. Supported methods are "nearest", "bilinear", and "bicubic". @@ -1167,6 +1252,7 @@ class DirectoryIterator(Iterator): save_prefix='', save_format='png', follow_links=False, + subset=None, interpolation='nearest'): if data_format is None: data_format = K.image_data_format() @@ -1200,7 +1286,20 @@ class DirectoryIterator(Iterator): self.save_format = save_format self.interpolation = interpolation - white_list_formats = {'png', 'jpg', 'jpeg', 'bmp', 'ppm'} + if subset is not None: + validation_split = self.image_data_generator.validation_split + if subset == 'validation': + split = (0, validation_split) + elif subset == 'training': + split = (validation_split, 1) + else: + raise ValueError('Invalid subset name: ', subset, + '; expected "training" or "validation"') + else: + split = None + self.subset = subset + + white_list_formats = {'png', 'jpg', 'jpeg', 'bmp', 'ppm', 'tif', 'tiff'} # first, count the number of samples and classes self.samples = 0 @@ -1217,7 +1316,8 @@ class DirectoryIterator(Iterator): function_partial = partial( _count_valid_files_in_directory, white_list_formats=white_list_formats, - follow_links=follow_links) + follow_links=follow_links, + split=split) self.samples = sum( pool.map(function_partial, (os.path.join(directory, subdir) for subdir in classes))) @@ -1233,14 +1333,15 @@ class DirectoryIterator(Iterator): i = 0 for dirpath in (os.path.join(directory, subdir) for subdir in classes): results.append( - pool.apply_async( - _list_valid_filenames_in_directory, - (dirpath, white_list_formats, self.class_indices, follow_links))) + pool.apply_async(_list_valid_filenames_in_directory, + (dirpath, white_list_formats, split, + self.class_indices, follow_links))) for res in results: classes, filenames = res.get() self.classes[i:i + len(classes)] = classes self.filenames += filenames i += len(classes) + pool.close() pool.join() super(DirectoryIterator, self).__init__(self.samples, batch_size, shuffle, diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/image_test.py b/tensorflow/python/keras/_impl/keras/preprocessing/image_test.py index c0790b5a51..001fee91f9 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/image_test.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/image_test.py @@ -20,6 +20,7 @@ from __future__ import print_function import os import shutil +import tempfile import numpy as np @@ -74,6 +75,7 @@ class TestImage(test.TestCase): shear_range=0.5, zoom_range=0.2, channel_shift_range=0., + brightness_range=(1, 5), fill_mode='nearest', cval=0.5, horizontal_flip=True, @@ -92,6 +94,47 @@ class TestImage(test.TestCase): self.assertEqual(x.shape[1:], images.shape[1:]) break + def test_image_data_generator_with_validation_split(self): + if PIL is None: + return # Skip test if PIL is not available. + + for test_images in _generate_test_images(): + img_list = [] + for im in test_images: + img_list.append(keras.preprocessing.image.img_to_array(im)[None, ...]) + + images = np.vstack(img_list) + generator = keras.preprocessing.image.ImageDataGenerator( + validation_split=0.5) + seq = generator.flow( + images, + np.arange(images.shape[0]), + shuffle=False, + batch_size=3, + subset='validation') + _, y = seq[0] + self.assertEqual(list(y), [0, 1, 2]) + seq = generator.flow( + images, + np.arange(images.shape[0]), + shuffle=False, + batch_size=3, + subset='training') + _, y2 = seq[0] + self.assertEqual(list(y2), [4, 5, 6]) + + with self.assertRaises(ValueError): + generator.flow( + images, + np.arange(images.shape[0]), + shuffle=False, + batch_size=3, + subset='foo') + + def test_image_data_generator_with_split_value_error(self): + with self.assertRaises(ValueError): + keras.preprocessing.image.ImageDataGenerator(validation_split=5) + def test_image_data_generator_invalid_data(self): generator = keras.preprocessing.image.ImageDataGenerator( featurewise_center=True, @@ -202,9 +245,80 @@ class TestImage(test.TestCase): # check number of classes and images self.assertEqual(len(dir_iterator.class_indices), num_classes) self.assertEqual(len(dir_iterator.classes), count) - self.assertEqual(sorted(dir_iterator.filenames), sorted(filenames)) + self.assertEqual(set(dir_iterator.filenames), set(filenames)) _ = dir_iterator.next() + def directory_iterator_with_validation_split_test_helper( + self, validation_split): + if PIL is None: + return # Skip test if PIL is not available. + + num_classes = 2 + tmp_folder = tempfile.mkdtemp(prefix='test_images') + + # create folders and subfolders + paths = [] + for cl in range(num_classes): + class_directory = 'class-{}'.format(cl) + classpaths = [ + class_directory, + os.path.join(class_directory, 'subfolder-1'), + os.path.join(class_directory, 'subfolder-2'), + os.path.join(class_directory, 'subfolder-1', 'sub-subfolder') + ] + for path in classpaths: + os.mkdir(os.path.join(tmp_folder, path)) + paths.append(classpaths) + + # save the images in the paths + count = 0 + filenames = [] + for test_images in _generate_test_images(): + for im in test_images: + # rotate image class + im_class = count % num_classes + # rotate subfolders + classpaths = paths[im_class] + filename = os.path.join(classpaths[count % len(classpaths)], + 'image-{}.jpg'.format(count)) + filenames.append(filename) + im.save(os.path.join(tmp_folder, filename)) + count += 1 + + # create iterator + generator = keras.preprocessing.image.ImageDataGenerator( + validation_split=validation_split) + + with self.assertRaises(ValueError): + generator.flow_from_directory(tmp_folder, subset='foo') + + num_validation = int(count * validation_split) + num_training = count - num_validation + train_iterator = generator.flow_from_directory( + tmp_folder, subset='training') + self.assertEqual(train_iterator.samples, num_training) + + valid_iterator = generator.flow_from_directory( + tmp_folder, subset='validation') + self.assertEqual(valid_iterator.samples, num_validation) + + # check number of classes and images + self.assertEqual(len(train_iterator.class_indices), num_classes) + self.assertEqual(len(train_iterator.classes), num_training) + self.assertEqual( + len(set(train_iterator.filenames) & set(filenames)), num_training) + + shutil.rmtree(tmp_folder) + + def test_directory_iterator_with_validation_split_25_percent(self): + self.directory_iterator_with_validation_split_test_helper(0.25) + + def test_directory_iterator_with_validation_split_40_percent(self): + self.directory_iterator_with_validation_split_test_helper(0.40) + + def test_directory_iterator_with_validation_split_50_percent(self): + self.directory_iterator_with_validation_split_test_helper(0.50) + def test_img_utils(self): if PIL is None: return # Skip test if PIL is not available. @@ -241,6 +355,41 @@ class TestImage(test.TestCase): x = keras.preprocessing.image.img_to_array(img, data_format='channels_last') self.assertEqual(x.shape, (height, width, 1)) + def test_batch_standardize(self): + if PIL is None: + return # Skip test if PIL is not available. + + # ImageDataGenerator.standardize should work on batches + for test_images in _generate_test_images(): + img_list = [] + for im in test_images: + img_list.append(keras.preprocessing.image.img_to_array(im)[None, ...]) + + images = np.vstack(img_list) + generator = keras.preprocessing.image.ImageDataGenerator( + featurewise_center=True, + samplewise_center=True, + featurewise_std_normalization=True, + samplewise_std_normalization=True, + zca_whitening=True, + rotation_range=90., + width_shift_range=0.1, + height_shift_range=0.1, + shear_range=0.5, + zoom_range=0.2, + channel_shift_range=0., + brightness_range=(1, 5), + fill_mode='nearest', + cval=0.5, + horizontal_flip=True, + vertical_flip=True) + generator.fit(images, augment=True) + + transformed = np.copy(images) + for i, im in enumerate(transformed): + transformed[i] = generator.random_transform(im) + transformed = generator.standardize(transformed) + def test_img_transforms(self): x = np.random.random((3, 200, 200)) _ = keras.preprocessing.image.random_rotation(x, 20) diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/sequence.py b/tensorflow/python/keras/_impl/keras/preprocessing/sequence.py index a423d96d3d..e68c171d9c 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/sequence.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/sequence.py @@ -22,6 +22,8 @@ import random import numpy as np from six.moves import range # pylint: disable=redefined-builtin + +from tensorflow.python.keras._impl.keras.utils.data_utils import Sequence from tensorflow.python.util.tf_export import tf_export @@ -32,29 +34,40 @@ def pad_sequences(sequences, padding='pre', truncating='pre', value=0.): - """Pads each sequence to the same length (length of the longest sequence). + """Pads sequences to the same length. + + This function transforms a list of + `num_samples` sequences (lists of integers) + into a 2D Numpy array of shape `(num_samples, num_timesteps)`. + `num_timesteps` is either the `maxlen` argument if provided, + or the length of the longest sequence otherwise. + + Sequences that are shorter than `num_timesteps` + are padded with `value` at the end. - If maxlen is provided, any sequence longer - than maxlen is truncated to maxlen. - Truncation happens off either the beginning (default) or - the end of the sequence. + Sequences longer than `num_timesteps` are truncated + so that they fit the desired length. + The position where padding or truncation happens is determined by + the arguments `padding` and `truncating`, respectively. - Supports post-padding and pre-padding (default). + Pre-padding is the default. Arguments: - sequences: list of lists where each element is a sequence - maxlen: int, maximum length - dtype: type to cast the resulting sequence. - padding: 'pre' or 'post', pad either before or after each sequence. - truncating: 'pre' or 'post', remove values from sequences larger than - maxlen either in the beginning or in the end of the sequence - value: float, value to pad the sequences to the desired value. + sequences: List of lists, where each element is a sequence. + maxlen: Int, maximum length of all sequences. + dtype: Type of the output sequences. + padding: String, 'pre' or 'post': + pad either before or after each sequence. + truncating: String, 'pre' or 'post': + remove values from sequences larger than + `maxlen`, either at the beginning or at the end of the sequences. + value: Float, padding value. Returns: - x: numpy array with dimensions (number_of_sequences, maxlen) + x: Numpy array with shape `(len(sequences), maxlen)` Raises: - ValueError: in case of invalid values for `truncating` or `padding`, + ValueError: In case of invalid values for `truncating` or `padding`, or in case of invalid shape for a `sequences` entry. """ if not hasattr(sequences, '__len__'): @@ -92,10 +105,9 @@ def pad_sequences(sequences, # check `trunc` has expected shape trunc = np.asarray(trunc, dtype=dtype) if trunc.shape[1:] != sample_shape: - raise ValueError( - 'Shape of sample %s of sequence at position %s is different from ' - 'expected shape %s' - % (trunc.shape[1:], idx, sample_shape)) + raise ValueError('Shape of sample %s of sequence at position %s ' + 'is different from expected shape %s' % + (trunc.shape[1:], idx, sample_shape)) if padding == 'post': x[idx, :len(trunc)] = trunc @@ -110,22 +122,26 @@ def pad_sequences(sequences, def make_sampling_table(size, sampling_factor=1e-5): """Generates a word rank-based probabilistic sampling table. - This generates an array where the ith element - is the probability that a word of rank i would be sampled, - according to the sampling distribution used in word2vec. + Used for generating the `sampling_table` argument for `skipgrams`. + `sampling_table[i]` is the probability of sampling + the word i-th most common word in a dataset + (more common words should be sampled less frequently, for balance). - The word2vec formula is: - p(word) = min(1, sqrt(word.frequency/sampling_factor) / - (word.frequency/sampling_factor)) + The sampling probabilities are generated according + to the sampling distribution used in word2vec: + + `p(word) = min(1, sqrt(word_frequency / sampling_factor) / (word_frequency / + sampling_factor))` We assume that the word frequencies follow Zipf's law (s=1) to derive a numerical approximation of frequency(rank): - frequency(rank) ~ 1/(rank * (log(rank) + gamma) + 1/2 - 1/(12*rank)) - where gamma is the Euler-Mascheroni constant. + + `frequency(rank) ~ 1/(rank * (log(rank) + gamma) + 1/2 - 1/(12*rank))` + where `gamma` is the Euler-Mascheroni constant. Arguments: - size: int, number of possible words to sample. - sampling_factor: the sampling factor in the word2vec formula. + size: Int, number of possible words to sample. + sampling_factor: The sampling factor in the word2vec formula. Returns: A 1D Numpy array of length `size` where the ith entry @@ -151,30 +167,37 @@ def skipgrams(sequence, seed=None): """Generates skipgram word pairs. - Takes a sequence (list of indexes of words), - returns couples of [word_index, other_word index] and labels (1s or 0s), - where label = 1 if 'other_word' belongs to the context of 'word', - and label=0 if 'other_word' is randomly sampled + This function transforms a sequence of word indexes (list of integers) + into tuples of words of the form: + + - (word, word in the same window), with label 1 (positive samples). + - (word, random word from the vocabulary), with label 0 (negative samples). + + Read more about Skipgram in this gnomic paper by Mikolov et al.: + [Efficient Estimation of Word Representations in + Vector Space](http://arxiv.org/pdf/1301.3781v3.pdf) Arguments: - sequence: a word sequence (sentence), encoded as a list + sequence: A word sequence (sentence), encoded as a list of word indices (integers). If using a `sampling_table`, word indices are expected to match the rank of the words in a reference dataset (e.g. 10 would encode the 10-th most frequently occurring token). Note that index 0 is expected to be a non-word and will be skipped. - vocabulary_size: int. maximum possible word index + 1 - window_size: int. actually half-window. - The window of a word wi will be [i-window_size, i+window_size+1] - negative_samples: float >= 0. 0 for no negative (=random) samples. - 1 for same number as positive samples. etc. - shuffle: whether to shuffle the word couples before returning them. + vocabulary_size: Int, maximum possible word index + 1 + window_size: Int, size of sampling windows (technically half-window). + The window of a word `w_i` will be + `[i - window_size, i + window_size+1]`. + negative_samples: Float >= 0. 0 for no negative (i.e. random) samples. + 1 for same number as positive samples. + shuffle: Whether to shuffle the word couples before returning them. categorical: bool. if False, labels will be - integers (eg. [0, 1, 1 .. ]), - if True labels will be categorical eg. [[1,0],[0,1],[0,1] .. ] + integers (eg. `[0, 1, 1 .. ]`), + if `True`, labels will be categorical, e.g. + `[[1,0],[0,1],[0,1] .. ]`. sampling_table: 1D array of size `vocabulary_size` where the entry i encodes the probability to sample a word of rank i. - seed: random seed. + seed: Random seed. Returns: couples, labels: where `couples` are int pairs and @@ -234,9 +257,9 @@ def _remove_long_seq(maxlen, seq, label): """Removes sequences that exceed the maximum length. Arguments: - maxlen: int, maximum length - seq: list of lists where each sublist is a sequence - label: list where each element is an integer + maxlen: Int, maximum length of the output sequences. + seq: List of lists, where each sublist is a sequence. + label: List where each element is an integer. Returns: new_seq, new_label: shortened lists for `seq` and `label`. @@ -247,3 +270,120 @@ def _remove_long_seq(maxlen, seq, label): new_seq.append(x) new_label.append(y) return new_seq, new_label + + +@tf_export('keras.preprocessing.sequence.TimeseriesGenerator') +class TimeseriesGenerator(Sequence): + """Utility class for generating batches of temporal data. + + This class takes in a sequence of data-points gathered at + equal intervals, along with time series parameters such as + stride, length of history, etc., to produce batches for + training/validation. + + Arguments: + data: Indexable generator (such as list or Numpy array) + containing consecutive data points (timesteps). + The data should be at 2D, and axis 0 is expected + to be the time dimension. + targets: Targets corresponding to timesteps in `data`. + It should have same length as `data`. + length: Length of the output sequences (in number of timesteps). + sampling_rate: Period between successive individual timesteps + within sequences. For rate `r`, timesteps + `data[i]`, `data[i-r]`, ... `data[i - length]` + are used for create a sample sequence. + stride: Period between successive output sequences. + For stride `s`, consecutive output samples would + be centered around `data[i]`, `data[i+s]`, `data[i+2*s]`, etc. + start_index, end_index: Data points earlier than `start_index` + or later than `end_index` will not be used in the output sequences. + This is useful to reserve part of the data for test or validation. + shuffle: Whether to shuffle output samples, + or instead draw them in chronological order. + reverse: Boolean: if `true`, timesteps in each output sample will be + in reverse chronological order. + batch_size: Number of timeseries samples in each batch + (except maybe the last one). + + Returns: + A [Sequence](/utils/#sequence) instance. + + Examples: + + ```python + from keras.preprocessing.sequence import TimeseriesGenerator + import numpy as np + + data = np.array([[i] for i in range(50)]) + targets = np.array([[i] for i in range(50)]) + + data_gen = TimeseriesGenerator(data, targets, + length=10, sampling_rate=2, + batch_size=2) + assert len(data_gen) == 20 + + batch_0 = data_gen[0] + x, y = batch_0 + assert np.array_equal(x, + np.array([[[0], [2], [4], [6], [8]], + [[1], [3], [5], [7], [9]]])) + assert np.array_equal(y, + np.array([[10], [11]])) + ``` + """ + + def __init__(self, + data, + targets, + length, + sampling_rate=1, + stride=1, + start_index=0, + end_index=None, + shuffle=False, + reverse=False, + batch_size=128): + self.data = data + self.targets = targets + self.length = length + self.sampling_rate = sampling_rate + self.stride = stride + self.start_index = start_index + length + if end_index is None: + end_index = len(data) - 1 + self.end_index = end_index + self.shuffle = shuffle + self.reverse = reverse + self.batch_size = batch_size + + def __len__(self): + length = int( + np.ceil((self.end_index - self.start_index) / + (self.batch_size * self.stride))) + return length if length >= 0 else 0 + + def _empty_batch(self, num_rows): + samples_shape = [num_rows, self.length // self.sampling_rate] + samples_shape.extend(self.data.shape[1:]) + targets_shape = [num_rows] + targets_shape.extend(self.targets.shape[1:]) + return np.empty(samples_shape), np.empty(targets_shape) + + def __getitem__(self, index): + if self.shuffle: + rows = np.random.randint( + self.start_index, self.end_index, size=self.batch_size) + else: + i = self.start_index + self.batch_size * self.stride * index + rows = np.arange(i, min(i + self.batch_size * self.stride, + self.end_index), self.stride) + + samples, targets = self._empty_batch(len(rows)) + for j in range(len(rows)): + indices = range(rows[j] - self.length, rows[j], self.sampling_rate) + samples[j] = self.data[indices] + targets[j] = self.targets[rows[j]] + if self.reverse: + return samples[:, ::-1, ...], targets + return samples, targets diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/sequence_test.py b/tensorflow/python/keras/_impl/keras/preprocessing/sequence_test.py index 4529e6e94f..b9bfdd0004 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/sequence_test.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/sequence_test.py @@ -84,15 +84,91 @@ class TestSequence(test.TestCase): couples, labels = keras.preprocessing.sequence.skipgrams( np.arange(3), vocabulary_size=3) for couple in couples: - assert couple[0] in [0, 1, 2] and couple[1] in [0, 1, 2] + self.assertIn(couple[0], [0, 1, 2]) + self.assertIn(couple[1], [0, 1, 2]) # test window size and categorical labels couples, labels = keras.preprocessing.sequence.skipgrams( np.arange(5), vocabulary_size=5, window_size=1, categorical=True) for couple in couples: - assert couple[0] - couple[1] <= 3 + self.assertLessEqual(couple[0] - couple[1], 3) for l in labels: - assert len(l) == 2 + self.assertEqual(len(l), 2) + + def test_TimeseriesGenerator(self): + data = np.array([[i] for i in range(50)]) + targets = np.array([[i] for i in range(50)]) + + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, targets, length=10, sampling_rate=2, batch_size=2) + self.assertEqual(len(data_gen), 20) + self.assertAllClose(data_gen[0][0], + np.array([[[0], [2], [4], [6], [8]], [[1], [3], [5], + [7], [9]]])) + self.assertAllClose(data_gen[0][1], np.array([[10], [11]])) + self.assertAllClose(data_gen[1][0], + np.array([[[2], [4], [6], [8], [10]], [[3], [5], [7], + [9], [11]]])) + self.assertAllClose(data_gen[1][1], np.array([[12], [13]])) + + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, targets, length=10, sampling_rate=2, reverse=True, batch_size=2) + self.assertEqual(len(data_gen), 20) + self.assertAllClose(data_gen[0][0], + np.array([[[8], [6], [4], [2], [0]], [[9], [7], [5], + [3], [1]]])) + self.assertAllClose(data_gen[0][1], np.array([[10], [11]])) + + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, targets, length=10, sampling_rate=2, shuffle=True, batch_size=1) + batch = data_gen[0] + r = batch[1][0][0] + self.assertAllClose(batch[0], + np.array([[[r - 10], [r - 8], [r - 6], [r - 4], + [r - 2]]])) + self.assertAllClose(batch[1], np.array([ + [r], + ])) + + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, targets, length=10, sampling_rate=2, stride=2, batch_size=2) + self.assertEqual(len(data_gen), 10) + self.assertAllClose(data_gen[1][0], + np.array([[[4], [6], [8], [10], [12]], [[6], [8], [10], + [12], [14]]])) + self.assertAllClose(data_gen[1][1], np.array([[14], [16]])) + + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, + targets, + length=10, + sampling_rate=2, + start_index=10, + end_index=30, + batch_size=2) + self.assertEqual(len(data_gen), 5) + self.assertAllClose(data_gen[0][0], + np.array([[[10], [12], [14], [16], [18]], + [[11], [13], [15], [17], [19]]])) + self.assertAllClose(data_gen[0][1], np.array([[20], [21]])) + + data = np.array([np.random.random_sample((1, 2, 3, 4)) for i in range(50)]) + targets = np.array([np.random.random_sample((3, 2, 1)) for i in range(50)]) + data_gen = keras.preprocessing.sequence.TimeseriesGenerator( + data, + targets, + length=10, + sampling_rate=2, + start_index=10, + end_index=30, + batch_size=2) + + self.assertEqual(len(data_gen), 5) + self.assertAllClose(data_gen[0][0], + np.array( + [np.array(data[10:19:2]), + np.array(data[11:20:2])])) + self.assertAllClose(data_gen[0][1], np.array([targets[20], targets[21]])) if __name__ == '__main__': diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/text.py b/tensorflow/python/keras/_impl/keras/preprocessing/text.py index 1e3828ccf1..f652f318f3 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/text.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/text.py @@ -91,6 +91,7 @@ def one_hot(text, text, n, hash_function=hash, filters=filters, lower=lower, split=split) +@tf_export('keras.preprocessing.text.hashing_trick') def hashing_trick(text, n, hash_function=None, @@ -187,21 +188,27 @@ class Tokenizer(object): self.document_count = 0 self.char_level = char_level self.oov_token = oov_token + self.index_docs = {} def fit_on_texts(self, texts): """Updates internal vocabulary based on a list of texts. + In the case where texts contains lists, we assume each entry of the lists + to be a token. + Required before using `texts_to_sequences` or `texts_to_matrix`. Arguments: texts: can be a list of strings, - or a generator of strings (for memory-efficiency) + a generator of strings (for memory-efficiency), + or a list of list of strings. """ - self.document_count = 0 for text in texts: self.document_count += 1 - seq = text if self.char_level else text_to_word_sequence( - text, self.filters, self.lower, self.split) + if self.char_level or isinstance(text, list): + seq = text + else: + seq = text_to_word_sequence(text, self.filters, self.lower, self.split) for w in seq: if w in self.word_counts: self.word_counts[w] += 1 @@ -226,7 +233,6 @@ class Tokenizer(object): if i is None: self.word_index[self.oov_token] = len(self.word_index) + 1 - self.index_docs = {} for w, c in list(self.word_docs.items()): self.index_docs[self.word_index[w]] = c @@ -240,8 +246,7 @@ class Tokenizer(object): sequences: A list of sequence. A "sequence" is a list of integer word indices. """ - self.document_count = len(sequences) - self.index_docs = {} + self.document_count += len(sequences) for seq in sequences: seq = set(seq) for i in seq: @@ -268,7 +273,11 @@ class Tokenizer(object): return res def texts_to_sequences_generator(self, texts): - """Transforms each text in texts in a sequence of integers. + """Transforms each text in `texts` in a sequence of integers. + + Each item in texts can also be a list, in which case we assume each item of + that list + to be a token. Only top "num_words" most frequent words will be taken into account. Only words known by the tokenizer will be taken into account. @@ -281,8 +290,10 @@ class Tokenizer(object): """ num_words = self.num_words for text in texts: - seq = text if self.char_level else text_to_word_sequence( - text, self.filters, self.lower, self.split) + if self.char_level or isinstance(text, list): + seq = text + else: + seq = text_to_word_sequence(text, self.filters, self.lower, self.split) vect = [] for w in seq: i = self.word_index.get(w) diff --git a/tensorflow/python/keras/_impl/keras/preprocessing/text_test.py b/tensorflow/python/keras/_impl/keras/preprocessing/text_test.py index a934e331c4..c6a267e57e 100644 --- a/tensorflow/python/keras/_impl/keras/preprocessing/text_test.py +++ b/tensorflow/python/keras/_impl/keras/preprocessing/text_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2016 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -80,17 +81,52 @@ class TestText(test.TestCase): x_train = ['This text has only known words'] x_test = ['This text has some unknown words'] # 2 OOVs: some, unknown - # Defalut, without OOV flag + # Default, without OOV flag tokenizer = keras.preprocessing.text.Tokenizer() tokenizer.fit_on_texts(x_train) x_test_seq = tokenizer.texts_to_sequences(x_test) - assert len(x_test_seq[0]) == 4 # discards 2 OOVs + self.assertEqual(len(x_test_seq[0]), 4) # discards 2 OOVs # With OOV feature tokenizer = keras.preprocessing.text.Tokenizer(oov_token='') tokenizer.fit_on_texts(x_train) x_test_seq = tokenizer.texts_to_sequences(x_test) - assert len(x_test_seq[0]) == 6 # OOVs marked in place + self.assertEqual(len(x_test_seq[0]), 6) # OOVs marked in place + + def test_sequential_fit(self): + texts = [ + 'The cat sat on the mat.', 'The dog sat on the log.', + 'Dogs and cats living together.' + ] + word_sequences = [['The', 'cat', 'is', 'sitting'], + ['The', 'dog', 'is', 'standing']] + tokenizer = keras.preprocessing.text.Tokenizer() + tokenizer.fit_on_texts(texts) + tokenizer.fit_on_texts(word_sequences) + + self.assertEqual(tokenizer.document_count, 5) + + tokenizer.texts_to_matrix(texts) + tokenizer.texts_to_matrix(word_sequences) + + def test_text_to_word_sequence(self): + text = 'hello! ? world!' + seq = keras.preprocessing.text.text_to_word_sequence(text) + self.assertEqual(seq, ['hello', 'world']) + + def test_text_to_word_sequence_unicode(self): + text = u'ali! veli? kırk dokuz elli' + seq = keras.preprocessing.text.text_to_word_sequence(text) + self.assertEqual(seq, [u'ali', u'veli', u'kırk', u'dokuz', u'elli']) + + def test_tokenizer_unicode(self): + texts = [ + u'ali veli kırk dokuz elli', u'ali veli kırk dokuz elli veli kırk dokuz' + ] + tokenizer = keras.preprocessing.text.Tokenizer(num_words=5) + tokenizer.fit_on_texts(texts) + + self.assertEqual(len(tokenizer.word_counts), 5) if __name__ == '__main__': diff --git a/tensorflow/python/keras/preprocessing/image/__init__.py b/tensorflow/python/keras/preprocessing/image/__init__.py index b96e767552..6aba5fc825 100644 --- a/tensorflow/python/keras/preprocessing/image/__init__.py +++ b/tensorflow/python/keras/preprocessing/image/__init__.py @@ -27,6 +27,7 @@ from tensorflow.python.keras._impl.keras.preprocessing.image import img_to_array from tensorflow.python.keras._impl.keras.preprocessing.image import Iterator from tensorflow.python.keras._impl.keras.preprocessing.image import load_img from tensorflow.python.keras._impl.keras.preprocessing.image import NumpyArrayIterator +from tensorflow.python.keras._impl.keras.preprocessing.image import random_brightness from tensorflow.python.keras._impl.keras.preprocessing.image import random_channel_shift from tensorflow.python.keras._impl.keras.preprocessing.image import random_rotation from tensorflow.python.keras._impl.keras.preprocessing.image import random_shear diff --git a/tensorflow/python/keras/preprocessing/sequence/__init__.py b/tensorflow/python/keras/preprocessing/sequence/__init__.py index 112f6af5e5..b7a7149cc4 100644 --- a/tensorflow/python/keras/preprocessing/sequence/__init__.py +++ b/tensorflow/python/keras/preprocessing/sequence/__init__.py @@ -21,6 +21,7 @@ from __future__ import print_function from tensorflow.python.keras._impl.keras.preprocessing.sequence import make_sampling_table from tensorflow.python.keras._impl.keras.preprocessing.sequence import pad_sequences from tensorflow.python.keras._impl.keras.preprocessing.sequence import skipgrams +from tensorflow.python.keras._impl.keras.preprocessing.sequence import TimeseriesGenerator del absolute_import del division diff --git a/tensorflow/python/keras/preprocessing/text/__init__.py b/tensorflow/python/keras/preprocessing/text/__init__.py index 5bf1a2fb21..000ad68a0c 100644 --- a/tensorflow/python/keras/preprocessing/text/__init__.py +++ b/tensorflow/python/keras/preprocessing/text/__init__.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.python.keras._impl.keras.preprocessing.text import hashing_trick from tensorflow.python.keras._impl.keras.preprocessing.text import one_hot from tensorflow.python.keras._impl.keras.preprocessing.text import text_to_word_sequence from tensorflow.python.keras._impl.keras.preprocessing.text import Tokenizer diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-directory-iterator.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-directory-iterator.pbtxt index 04174bff5f..ec0f3d892d 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-directory-iterator.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-directory-iterator.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'directory\', \'image_data_generator\', \'target_size\', \'color_mode\', \'classes\', \'class_mode\', \'batch_size\', \'shuffle\', \'seed\', \'data_format\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'follow_links\', \'interpolation\'], varargs=None, keywords=None, defaults=[\'(256, 256)\', \'rgb\', \'None\', \'categorical\', \'32\', \'True\', \'None\', \'None\', \'None\', \'\', \'png\', \'False\', \'nearest\'], " + argspec: "args=[\'self\', \'directory\', \'image_data_generator\', \'target_size\', \'color_mode\', \'classes\', \'class_mode\', \'batch_size\', \'shuffle\', \'seed\', \'data_format\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'follow_links\', \'subset\', \'interpolation\'], varargs=None, keywords=None, defaults=[\'(256, 256)\', \'rgb\', \'None\', \'categorical\', \'32\', \'True\', \'None\', \'None\', \'None\', \'\', \'png\', \'False\', \'None\', \'nearest\'], " } member_method { name: "next" diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-image-data-generator.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-image-data-generator.pbtxt index 41f27d1f74..f5bc04e44c 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-image-data-generator.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-image-data-generator.pbtxt @@ -4,7 +4,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'featurewise_center\', \'samplewise_center\', \'featurewise_std_normalization\', \'samplewise_std_normalization\', \'zca_whitening\', \'zca_epsilon\', \'rotation_range\', \'width_shift_range\', \'height_shift_range\', \'shear_range\', \'zoom_range\', \'channel_shift_range\', \'fill_mode\', \'cval\', \'horizontal_flip\', \'vertical_flip\', \'rescale\', \'preprocessing_function\', \'data_format\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'False\', \'False\', \'False\', \'1e-06\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'nearest\', \'0.0\', \'False\', \'False\', \'None\', \'None\', \'None\'], " + argspec: "args=[\'self\', \'featurewise_center\', \'samplewise_center\', \'featurewise_std_normalization\', \'samplewise_std_normalization\', \'zca_whitening\', \'zca_epsilon\', \'rotation_range\', \'width_shift_range\', \'height_shift_range\', \'brightness_range\', \'shear_range\', \'zoom_range\', \'channel_shift_range\', \'fill_mode\', \'cval\', \'horizontal_flip\', \'vertical_flip\', \'rescale\', \'preprocessing_function\', \'data_format\', \'validation_split\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'False\', \'False\', \'False\', \'1e-06\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'0.0\', \'0.0\', \'0.0\', \'nearest\', \'0.0\', \'False\', \'False\', \'None\', \'None\', \'None\', \'0.0\'], " } member_method { name: "fit" @@ -12,11 +12,11 @@ tf_class { } member_method { name: "flow" - argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'shuffle\', \'seed\', \'save_to_dir\', \'save_prefix\', \'save_format\'], varargs=None, keywords=None, defaults=[\'None\', \'32\', \'True\', \'None\', \'None\', \'\', \'png\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'batch_size\', \'shuffle\', \'seed\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'subset\'], varargs=None, keywords=None, defaults=[\'None\', \'32\', \'True\', \'None\', \'None\', \'\', \'png\', \'None\'], " } member_method { name: "flow_from_directory" - argspec: "args=[\'self\', \'directory\', \'target_size\', \'color_mode\', \'classes\', \'class_mode\', \'batch_size\', \'shuffle\', \'seed\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'follow_links\', \'interpolation\'], varargs=None, keywords=None, defaults=[\'(256, 256)\', \'rgb\', \'None\', \'categorical\', \'32\', \'True\', \'None\', \'None\', \'\', \'png\', \'False\', \'nearest\'], " + argspec: "args=[\'self\', \'directory\', \'target_size\', \'color_mode\', \'classes\', \'class_mode\', \'batch_size\', \'shuffle\', \'seed\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'follow_links\', \'subset\', \'interpolation\'], varargs=None, keywords=None, defaults=[\'(256, 256)\', \'rgb\', \'None\', \'categorical\', \'32\', \'True\', \'None\', \'None\', \'\', \'png\', \'False\', \'None\', \'nearest\'], " } member_method { name: "random_transform" diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-numpy-array-iterator.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-numpy-array-iterator.pbtxt index 4ef6e6e99e..42196ddeee 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-numpy-array-iterator.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.-numpy-array-iterator.pbtxt @@ -6,7 +6,7 @@ tf_class { is_instance: "" member_method { name: "__init__" - argspec: "args=[\'self\', \'x\', \'y\', \'image_data_generator\', \'batch_size\', \'shuffle\', \'seed\', \'data_format\', \'save_to_dir\', \'save_prefix\', \'save_format\'], varargs=None, keywords=None, defaults=[\'32\', \'False\', \'None\', \'None\', \'None\', \'\', \'png\'], " + argspec: "args=[\'self\', \'x\', \'y\', \'image_data_generator\', \'batch_size\', \'shuffle\', \'seed\', \'data_format\', \'save_to_dir\', \'save_prefix\', \'save_format\', \'subset\'], varargs=None, keywords=None, defaults=[\'32\', \'False\', \'None\', \'None\', \'None\', \'\', \'png\', \'None\'], " } member_method { name: "next" diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.pbtxt index d28fef6965..6b850dd6b7 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.image.pbtxt @@ -36,6 +36,10 @@ tf_module { name: "load_img" argspec: "args=[\'path\', \'grayscale\', \'target_size\', \'interpolation\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'nearest\'], " } + member_method { + name: "random_brightness" + argspec: "args=[\'x\', \'brightness_range\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "random_channel_shift" argspec: "args=[\'x\', \'intensity\', \'channel_axis\'], varargs=None, keywords=None, defaults=[\'0\'], " diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.-timeseries-generator.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.-timeseries-generator.pbtxt new file mode 100644 index 0000000000..d9c3215b55 --- /dev/null +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.-timeseries-generator.pbtxt @@ -0,0 +1,14 @@ +path: "tensorflow.keras.preprocessing.sequence.TimeseriesGenerator" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member_method { + name: "__init__" + argspec: "args=[\'self\', \'data\', \'targets\', \'length\', \'sampling_rate\', \'stride\', \'start_index\', \'end_index\', \'shuffle\', \'reverse\', \'batch_size\'], varargs=None, keywords=None, defaults=[\'1\', \'1\', \'0\', \'None\', \'False\', \'False\', \'128\'], " + } + member_method { + name: "on_epoch_end" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.pbtxt index 1b01935cc5..cf59f8a272 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.sequence.pbtxt @@ -1,5 +1,9 @@ path: "tensorflow.keras.preprocessing.sequence" tf_module { + member { + name: "TimeseriesGenerator" + mtype: "" + } member_method { name: "make_sampling_table" argspec: "args=[\'size\', \'sampling_factor\'], varargs=None, keywords=None, defaults=[\'1e-05\'], " diff --git a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.text.pbtxt b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.text.pbtxt index d106429df0..50b54fc7e1 100644 --- a/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.text.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.keras.preprocessing.text.pbtxt @@ -4,6 +4,10 @@ tf_module { name: "Tokenizer" mtype: "" } + member_method { + name: "hashing_trick" + argspec: "args=[\'text\', \'n\', \'hash_function\', \'filters\', \'lower\', \'split\'], varargs=None, keywords=None, defaults=[\'None\', \'!\"#$%&()*+,-./:;<=>?@[\\\\]^_`{|}~\\t\\n\', \'True\', \' \'], " + } member_method { name: "one_hot" argspec: "args=[\'text\', \'n\', \'filters\', \'lower\', \'split\'], varargs=None, keywords=None, defaults=[\'!\"#$%&()*+,-./:;<=>?@[\\\\]^_`{|}~\\t\\n\', \'True\', \' \'], " -- GitLab From 1004396a769ad9fdf350ed28083bca5b6ad00402 Mon Sep 17 00:00:00 2001 From: Akshay Agrawal Date: Thu, 22 Mar 2018 14:24:23 -0700 Subject: [PATCH 099/906] Remove use of deprecated API from RNN Colorbot example. PiperOrigin-RevId: 190125356 --- .../examples/rnn_colorbot/rnn_colorbot.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py b/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py index 29f0232454..88fffc962f 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py +++ b/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py @@ -60,6 +60,7 @@ import functools import os import sys import time +import urllib import six import tensorflow as tf @@ -89,13 +90,35 @@ def parse(line): return rgb, chars, length +def maybe_download(filename, work_directory, source_url): + """Download the data from source url, unless it's already here. + + Args: + filename: string, name of the file in the directory. + work_directory: string, path to working directory. + source_url: url to download from if file doesn't exist. + + Returns: + Path to resulting file. + """ + if not tf.gfile.Exists(work_directory): + tf.gfile.MakeDirs(work_directory) + filepath = os.path.join(work_directory, filename) + if not tf.gfile.Exists(filepath): + temp_file_name, _ = urllib.request.urlretrieve(source_url) + tf.gfile.Copy(temp_file_name, filepath) + with tf.gfile.GFile(filepath) as f: + size = f.size() + print("Successfully downloaded", filename, size, "bytes.") + return filepath + + def load_dataset(data_dir, url, batch_size): """Loads the colors data at path into a PaddedDataset.""" # Downloads data at url into data_dir/basename(url). The dataset has a header # row (color_name, r, g, b) followed by comma-separated lines. - path = tf.contrib.learn.datasets.base.maybe_download( - os.path.basename(url), data_dir, url) + path = maybe_download(os.path.basename(url), data_dir, url) # This chain of commands loads our data by: # 1. skipping the header; (.skip(1)) -- GitLab From a34a3b2035ca0cfd48488c03bd4b088070bf9a25 Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Thu, 22 Mar 2018 14:32:12 -0700 Subject: [PATCH 100/906] Fixing the issue where MKL-DNN is getting built when not using --config=mkl --- tensorflow/tensorflow.bzl | 53 +++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 9b0db8a112..8549c34691 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -788,7 +788,33 @@ def tf_cc_test_mkl(srcs, tags=[], size="medium", args=None): - if_mkl(tf_cc_tests(srcs, deps, name, linkstatic=linkstatic, tags=tags, size=size, args=args, nocopts="-fno-exceptions")) + for src in srcs: + native.cc_test( + name=src_to_test_name(src), + srcs=if_mkl([src]) + tf_binary_additional_srcs(), + copts=tf_copts(), + linkopts=select({ + clean_dep("//tensorflow:android"): [ + "-pie", + ], + clean_dep("//tensorflow:windows"): [], + clean_dep("//tensorflow:windows_msvc"): [], + "//conditions:default": [ + "-lpthread", + "-lm" + ], + }) + _rpath_linkopts(src_to_test_name(src)), + deps=deps + if_mkl( + [ + "//third_party/mkl:intel_binary_blob", + ], + ), + linkstatic=linkstatic, + tags=tags, + size=size, + args=args, + nocopts="-fno-exceptions") + def tf_cc_tests_gpu(srcs, deps, @@ -1006,16 +1032,12 @@ register_extension_info( def tf_mkl_kernel_library(name, prefix=None, srcs=None, - gpu_srcs=None, hdrs=None, deps=None, alwayslink=1, copts=tf_copts(), - nocopts="-fno-exceptions", - **kwargs): + nocopts="-fno-exceptions"): """A rule to build MKL-based TensorFlow kernel libraries.""" - gpu_srcs = gpu_srcs # unused argument - kwargs = kwargs # unused argument if not bool(srcs): srcs = [] @@ -1028,16 +1050,15 @@ def tf_mkl_kernel_library(name, hdrs = hdrs + native.glob( [prefix + "*.h"]) - if_mkl( - native.cc_library( - name=name, - srcs=srcs, - hdrs=hdrs, - deps=deps, - alwayslink=alwayslink, - copts=copts, - nocopts=nocopts - )) + native.cc_library( + name=name, + srcs=if_mkl(srcs), + hdrs=hdrs, + deps=deps, + alwayslink=alwayslink, + copts=copts, + nocopts=nocopts + ) register_extension_info( extension_name = "tf_mkl_kernel_library", -- GitLab From 1a99109e8832bc94710d2dcfb5d9525688913a50 Mon Sep 17 00:00:00 2001 From: Dimitris Vardoulakis Date: Thu, 22 Mar 2018 14:38:41 -0700 Subject: [PATCH 101/906] Merge consecutive broadcast HLO instructions. As an optimization, replace consecutive broadcast instructions with a single equivalent broadcast in algebraic simplification. PiperOrigin-RevId: 190127730 --- .../xla/service/algebraic_simplifier.cc | 22 ++++++-- .../xla/service/algebraic_simplifier_test.cc | 51 +++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 971c2935c8..88f6ff0a07 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -1121,10 +1121,10 @@ bool OutputIsSubsetOfOperandElements(HloInstruction* instruction, Status AlgebraicSimplifierVisitor::HandleBroadcast(HloInstruction* broadcast) { auto operand = broadcast->mutable_operand(0); + auto dims = broadcast->dimensions(); // A degenerate broadcast of a reshape that does not change the number of // elements can be replaced by a reshape. - if (std::is_sorted(broadcast->dimensions().begin(), - broadcast->dimensions().end()) && + if (std::is_sorted(dims.begin(), dims.end()) && ShapeUtil::ElementsIn(broadcast->shape()) == ShapeUtil::ElementsIn(operand->shape())) { VLOG(10) << "transform broadcast(X) -> reshape(X) where " @@ -1142,8 +1142,8 @@ Status AlgebraicSimplifierVisitor::HandleBroadcast(HloInstruction* broadcast) { VLOG(10) << "transform broadcast(X) -> transpose(X) where " "n(broadcast(X)) == n(X)"; return ReplaceWithNewInstruction( - broadcast, HloInstruction::CreateTranspose(broadcast->shape(), operand, - broadcast->dimensions())); + broadcast, + HloInstruction::CreateTranspose(broadcast->shape(), operand, dims)); } // A broadcast of a reshape which merely inserts 1-sized dimensions can @@ -1157,7 +1157,6 @@ Status AlgebraicSimplifierVisitor::HandleBroadcast(HloInstruction* broadcast) { if (merely_inserts_or_deletes_1_sized_dimensions && deleted_indices.empty()) { std::reverse(inserted_indices.begin(), inserted_indices.end()); - auto dims = broadcast->dimensions(); for (auto inserted_index : inserted_indices) { dims.erase(dims.begin() + inserted_index); } @@ -1201,6 +1200,19 @@ Status AlgebraicSimplifierVisitor::HandleBroadcast(HloInstruction* broadcast) { return user->ReplaceAllUsesWith(new_broadcast); } } + return Status::OK(); + } + + // Merge two consecutive broadcasts into a single one. + if (operand->opcode() == HloOpcode::kBroadcast) { + std::vector new_dimensions(operand->dimensions().size()); + for (auto dim : operand->dimensions()) { + new_dimensions.push_back(dims[dim]); + } + return ReplaceWithNewInstruction( + broadcast, + HloInstruction::CreateBroadcast( + broadcast->shape(), operand->mutable_operand(0), new_dimensions)); } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index 451294ef5d..3b80a827bf 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -35,6 +35,8 @@ limitations under the License. #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/strings/str_util.h" +using ::testing::ElementsAre; + namespace xla { namespace { @@ -2462,6 +2464,55 @@ TEST_F(AlgebraicSimplifierTest, TrivialDynamicUpdateSlice) { op::DynamicSlice(op::Parameter(), op::Parameter())); } +// Test that two consecutive broadcasts can be merged to one. +TEST_F(AlgebraicSimplifierTest, MergeBroadcasts) { + HloComputation::Builder builder(TestName()); + Shape r2f32 = ShapeUtil::MakeShape(F32, {2, 2}); + HloInstruction* input_array = builder.AddInstruction( + HloInstruction::CreateConstant(Literal::CreateR1({3, 4}))); + HloInstruction* inner_bcast = builder.AddInstruction( + HloInstruction::CreateBroadcast(r2f32, input_array, {1})); + Shape r3f32 = ShapeUtil::MakeShape(F32, {2, 2, 2}); + builder.AddInstruction( + HloInstruction::CreateBroadcast(r3f32, inner_bcast, {0, 2})); + + auto computation = module().AddEntryComputation(builder.Build()); + HloInstruction* root = computation->root_instruction(); + EXPECT_EQ(root->opcode(), HloOpcode::kBroadcast); + AlgebraicSimplifier simplifier(/*is_layout_sensitive=*/false, + non_bitcasting_callback()); + ASSERT_TRUE(simplifier.Run(&module()).ValueOrDie()); + root = computation->root_instruction(); + EXPECT_THAT(root, op::Broadcast(op::Constant())); + EXPECT_THAT(root->dimensions(), ElementsAre(2)); +} + +// Test that two consecutive broadcasts can be merged to one. +TEST_F(AlgebraicSimplifierTest, MergeBroadcasts2) { + HloComputation::Builder builder(TestName()); + Shape r2f32 = ShapeUtil::MakeShape(F32, {2, 3}); + Shape r3f32 = ShapeUtil::MakeShape(F32, {2, 5, 3}); + HloInstruction* param0 = builder.AddInstruction( + HloInstruction::CreateParameter(0, r2f32, "param0")); + // The initial dimensions go to places 0 and 2 in the 3-dim array, + // and to places 1 and 3 in the 4-dim array, + HloInstruction* inner_bcast = builder.AddInstruction( + HloInstruction::CreateBroadcast(r3f32, param0, {0, 2})); + Shape r4f32 = ShapeUtil::MakeShape(F32, {4, 2, 5, 3}); + builder.AddInstruction( + HloInstruction::CreateBroadcast(r4f32, inner_bcast, {1, 2, 3})); + + auto computation = module().AddEntryComputation(builder.Build()); + HloInstruction* root = computation->root_instruction(); + EXPECT_EQ(root->opcode(), HloOpcode::kBroadcast); + AlgebraicSimplifier simplifier(/*is_layout_sensitive=*/false, + non_bitcasting_callback()); + ASSERT_TRUE(simplifier.Run(&module()).ValueOrDie()); + root = computation->root_instruction(); + EXPECT_THAT(root, op::Broadcast(op::Parameter(0))); + EXPECT_THAT(root->dimensions(), ElementsAre(1, 3)); +} + struct PadReduceWindowEffectiveBroadcastCase { std::vector input_spatials; std::vector symmetric_pad_spatials; -- GitLab From 730e69519a93a668d97ea298d52365326c00357d Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Thu, 22 Mar 2018 14:47:22 -0700 Subject: [PATCH 102/906] Automated g4 rollback of changelist 190021164 PiperOrigin-RevId: 190129094 --- tensorflow/c/eager/BUILD | 2 + tensorflow/c/eager/c_api.cc | 194 ++++++----------- tensorflow/c/eager/c_api_internal.h | 84 +------- tensorflow/core/common_runtime/eager/BUILD | 22 ++ .../core/common_runtime/eager/context.cc | 153 ++++++++++++++ .../core/common_runtime/eager/context.h | 198 ++++++++++++++++++ 6 files changed, 450 insertions(+), 203 deletions(-) create mode 100644 tensorflow/core/common_runtime/eager/context.cc create mode 100644 tensorflow/core/common_runtime/eager/context.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index 841ff48a38..bea5a121b3 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -28,6 +28,7 @@ tf_cuda_library( "//tensorflow/c:c_api", "//tensorflow/c:c_api_internal", "//tensorflow/core:core_cpu", + "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", "//tensorflow/core:core_cpu_internal", @@ -64,6 +65,7 @@ tf_cuda_library( "//tensorflow/core:framework_lite", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", ], diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index a23015c99e..2402a6d044 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -71,18 +71,6 @@ std::atomic_int_fast64_t func_id_generator(0); } // namespace -TFE_ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy) { - if (!soft_placement) { - return original_policy; - } - if (original_policy == TFE_DEVICE_PLACEMENT_EXPLICIT || - original_policy == TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32) { - return TFE_DEVICE_PLACEMENT_SILENT; - } - return original_policy; -} - extern "C" { TFE_ContextOptions* TFE_NewContextOptions() { return new TFE_ContextOptions; } @@ -104,19 +92,7 @@ void TFE_ContextOptionsSetDevicePlacementPolicy( TF_CAPI_EXPORT extern void TFE_ContextSetAsyncForThread(TFE_Context* ctx, unsigned char async, TF_Status* status) { - { - tensorflow::mutex_lock l(ctx->async_map_mu); - ctx->thread_local_async[std::this_thread::get_id()] = async; - } - if (async) { - ctx->executor.EnableAsync(); - } else { - // TODO(agarwal): Currently we add a wait here to handle cases where a sync - // op has a control dependency on an async op, and the latter has not - // executed yet. This wait can be removed by storing all the control inputs - // and waiting for them when executing ops. - status->status = ctx->executor.WaitForAllPendingNodes(); - } + status->status = ctx->context.SetAsyncForThread(async); } void TFE_DeleteContextOptions(TFE_ContextOptions* options) { delete options; } @@ -133,34 +109,26 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) { new tensorflow::DeviceMgr(devices)); tensorflow::Rendezvous* r = new tensorflow::IntraProcessRendezvous(device_mgr.get()); - return new TFE_Context(*opts, std::move(device_mgr), r); + return new TFE_Context(opts->session_options.options, opts->policy, + opts->async, std::move(device_mgr), r); } void TFE_DeleteContext(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.WaitForAllPendingNodes(); - { - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); - } - ctx->rendezvous->Unref(); delete ctx; } TF_DeviceList* TFE_ContextListDevices(TFE_Context* ctx, TF_Status* status) { TF_DeviceList* list = new TF_DeviceList; - ctx->device_manager->ListDeviceAttributes(&list->response); + ctx->context.device_mgr()->ListDeviceAttributes(&list->response); return list; } -void TFE_ContextClearCaches(TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::STLDeleteValues(&ctx->kernel_cache); -} +void TFE_ContextClearCaches(TFE_Context* ctx) { ctx->context.ClearCaches(); } void TFE_ContextSetThreadLocalDevicePlacementPolicy( TFE_Context* ctx, TFE_ContextDevicePlacementPolicy policy) { - tensorflow::mutex_lock ml(ctx->policy_map_mu); - ctx->thread_local_policies[std::this_thread::get_id()] = policy; + ctx->context.SetThreadLocalDevicePlacementPolicy( + static_cast(policy)); } // Note: this function looks up a thread local policy. So it should be called in @@ -168,25 +136,20 @@ void TFE_ContextSetThreadLocalDevicePlacementPolicy( // safe to call this function from the async EagerExecutor threads. extern TFE_ContextDevicePlacementPolicy TFE_ContextGetDevicePlacementPolicy( TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->policy_map_mu); - auto policy_map_it = - ctx->thread_local_policies.find(std::this_thread::get_id()); - if (policy_map_it != ctx->thread_local_policies.end()) { - return policy_map_it->second; - } - return ctx->policy; + return static_cast( + ctx->context.GetDevicePlacementPolicy()); } void TFE_ContextAsyncWait(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.WaitForAllPendingNodes(); + status->status = ctx->context.AsyncWait(); } void TFE_ContextGetStatus(TFE_Context* ctx, TF_Status* status) { - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); } void TFE_ContextAsyncClearError(TFE_Context* ctx) { - ctx->executor.ClearError(); + ctx->context.ClearAsyncError(); } TFE_TensorHandle* TFE_NewTensorHandle(TF_Tensor* t, TF_Status* status) { @@ -259,7 +222,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, // nullptr. tensorflow::Device* src_opd = nullptr; TF_RETURN_IF_ERROR(h->TensorAndDevice(&src, &srcd, &src_opd)); - if (srcd == nullptr) srcd = ctx->devices[0]; + if (srcd == nullptr) srcd = ctx->context.HostCPU(); bool is_same_device = (srcd == dstd) || (DeviceName(srcd) == DeviceName(dstd)); const bool dst_cpu = IsCPU(dstd); @@ -332,8 +295,7 @@ TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, status->status = tensorflow::AttrTypeMapForOp(name, &types); if (status->status.ok()) return new TFE_Op(ctx, name, types); if (TF_GetCode(status) == TF_NOT_FOUND) { - tensorflow::mutex_lock l(ctx->functions_mu); - if (ctx->func_lib_def.Find(name) != nullptr) { + if (ctx->context.FindFunctionByName(name)) { status->status = tensorflow::Status::OK(); return new TFE_Op(ctx, name, nullptr); } @@ -346,20 +308,14 @@ void TFE_DeleteOp(TFE_Op* op) { delete op; } void TFE_OpSetDevice(TFE_Op* op, const char* device_name, TF_Status* status) { tensorflow::Device* d = nullptr; if (device_name != nullptr && strlen(device_name) > 0) { - auto it = op->ctx->devices_map.find(device_name); - if (it == op->ctx->devices_map.end()) { - status->status = - tensorflow::errors::InvalidArgument(device_name, " unknown device."); - return; - } - d = it->second; + status->status = op->ctx->context.FindDeviceByName(device_name, &d); } op->device = d; } const char* TFE_OpGetDevice(TFE_Op* op, TF_Status* status) { tensorflow::Device* device = - (op->device == nullptr) ? op->ctx->devices[0] : op->device; + (op->device == nullptr) ? op->ctx->context.HostCPU() : op->device; return device->name().c_str(); } @@ -634,7 +590,7 @@ tensorflow::Status ValidateInputTypeAndPlacement( tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, TFE_Context* ctx, TF_Status* status) { tensorflow::DeviceSet ds; - for (tensorflow::Device* d : ctx->devices) { + for (tensorflow::Device* d : *ctx->context.devices()) { ds.AddDevice(d); } tensorflow::DeviceTypeVector final_devices; @@ -648,7 +604,7 @@ tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, "Could not find valid device for node ", ndef.DebugString()); return nullptr; } - for (tensorflow::Device* d : ctx->devices) { + for (tensorflow::Device* d : *ctx->context.devices()) { if (d->device_type() == final_devices[0].type_string()) { return d; } @@ -663,9 +619,8 @@ tensorflow::Status Execute( const tensorflow::gtl::InlinedVector& op_inputs, tensorflow::KernelAndDevice* kernel, tensorflow::NodeExecStats* maybe_stats, TFE_TensorHandle** retvals, int num_retvals) { - if (!ctx->soft_placement && device == nullptr) { - // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU - device = ctx->devices[0]; + if (!ctx->context.SoftPlacement() && device == nullptr) { + device = ctx->context.HostCPU(); } if (device == nullptr) { @@ -684,8 +639,8 @@ tensorflow::Status Execute( inputs[i] = *input_tensor; } // WARNING: kernel->Run utilizes the FunctionLibraryRuntime - // (ctx->func_lib(device)), which in turn holds a pointer to func_lib_def, - // which is GUARDED_BY(ctx->functions_mu). But knowledge of the implementation + // (ctx->func_lib(device)), which in turn holds a pointer to func_lib_def. + // But knowledge of the implementation // of FunctionLibraryRuntime tells us that func_lib_def is not accessed by // FunctionLibraryRuntime::Run(), so there is no thread-safety concern here. // This is quite subtle. Re-work things to make this better? (Would it make @@ -697,18 +652,18 @@ tensorflow::Status Execute( if (maybe_stats != nullptr) { maybe_stats->set_op_end_rel_micros(tensorflow::Env::Default()->NowMicros() - maybe_stats->all_start_micros()); - tensorflow::mutex_lock ml(ctx->metadata_mu); - if (ctx->should_store_metadata.load()) { - auto* step_stats = ctx->run_metadata.mutable_step_stats(); + tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); + if (ctx->context.ShouldStoreMetadata()) { + auto* step_stats = ctx->context.RunMetadataProto()->mutable_step_stats(); // Lazily initialize the RunMetadata with information about all devices if // this is the first call. - while (step_stats->dev_stats_size() < ctx->devices.size()) { + while (step_stats->dev_stats_size() < ctx->context.devices()->size()) { step_stats->add_dev_stats(); } // Find the current device's index. int device_idx = 0; - for (int i = 0; i < ctx->devices.size(); ++i) { - if (ctx->devices[i] == device) { + for (int i = 0; i < ctx->context.devices()->size(); ++i) { + if (ctx->context.devices()->at(i) == device) { device_idx = i; break; } @@ -744,7 +699,7 @@ class ExecuteNode : public tensorflow::EagerNode { tensorflow::NodeExecStats* maybe_stats, const tensorflow::DataTypeVector& output_dtypes, TFE_TensorHandle** retvals, int num_retvals) - : tensorflow::EagerNode(op->ctx->executor.NextId()), + : tensorflow::EagerNode(op->ctx->context.NextId()), ctx_(op->ctx), op_device_(op->device), inputs_(op->inputs), @@ -800,7 +755,7 @@ class CopyToDeviceNode : public tensorflow::EagerNode { public: CopyToDeviceNode(TFE_TensorHandle* src, tensorflow::Device* dstd, TFE_Context* ctx) - : tensorflow::EagerNode(ctx->executor.NextId()), + : tensorflow::EagerNode(ctx->context.NextId()), src_(src), dstd_(dstd), ctx_(ctx), @@ -866,8 +821,7 @@ const tensorflow::FunctionDef* OpToFunction( TFE_Context* ctx = op->ctx; const tensorflow::OpRegistrationData* op_data; { - tensorflow::tf_shared_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.LookUp(op->name, &op_data); + status->status = ctx->context.FindFunctionOpData(op->name, &op_data); if (!status->status.ok()) { return nullptr; } @@ -963,10 +917,9 @@ const tensorflow::FunctionDef* OpToFunction( } VLOG(1) << "Fixed Output names and all types: " << fdef.DebugString(); - tensorflow::mutex_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.AddFunctionDef(fdef); + ctx->context.AddFunctionDef(fdef); if (!status->status.ok()) return nullptr; - const auto ret = ctx->func_lib_def.Find(signature->name()); + const auto ret = ctx->context.FindFunctionDef(signature->name()); DCHECK(ret != nullptr); return ret; } @@ -985,8 +938,7 @@ std::unique_ptr BuildXlaLaunch(TFE_Op* op, TF_Status* status) { const tensorflow::FunctionDef* fdef; { - tensorflow::tf_shared_lock l(op->ctx->functions_mu); - fdef = op->ctx->func_lib_def.Find(op->name); + fdef = op->ctx->context.FindFunctionDef(op->name); } std::vector const_input_types; std::vector arg_input_types; @@ -1063,7 +1015,7 @@ extern "C" { void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, TF_Status* status) { TFE_Context* ctx = op->ctx; - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); if (!status->status.ok()) { return; } @@ -1087,7 +1039,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (op->inputs[i]->dtype == tensorflow::DT_RESOURCE && input_op_device != op->device) { tensorflow::Device* d = - input_op_device == nullptr ? ctx->devices[0] : input_op_device; + input_op_device == nullptr ? ctx->context.HostCPU() : input_op_device; VLOG(1) << "Changing device of operation " << op->name << " to " << d->name() << " because input #" << i << " is a resource in this device."; @@ -1095,40 +1047,35 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, } } tensorflow::Device* device = op->device; - if (!ctx->soft_placement && device == nullptr) { - // TODO(ashankar): ASSUMPTION: ctx->devices[0] is always CPU - device = ctx->devices[0]; + if (!ctx->context.SoftPlacement() && device == nullptr) { + device = ctx->context.HostCPU(); } tensorflow::Fprint128 cache_key = op->attrs.CacheKey(device == nullptr ? "unspecified" : device->name()); - tensorflow::KernelAndDevice* kernel; - { - tensorflow::tf_shared_lock l(ctx->cache_mu); - kernel = tensorflow::gtl::FindPtrOrNull(ctx->kernel_cache, cache_key); - } + tensorflow::KernelAndDevice* kernel = ctx->context.GetCachedKernel(cache_key); if (kernel == nullptr) { const tensorflow::NodeDef& ndef = op->attrs.BuildNodeDef(); - if (ctx->soft_placement && device == nullptr) { + if (ctx->context.SoftPlacement() && device == nullptr) { device = SelectDevice(ndef, ctx, status); if (!status->status.ok()) { return; } } CHECK(device != nullptr); - if (ctx->log_device_placement) { + if (ctx->context.LogDevicePlacement()) { LOG(INFO) << "Executing op " << ndef.op() << " in device " << device->name(); } - kernel = new tensorflow::KernelAndDevice(ctx->rendezvous); + kernel = new tensorflow::KernelAndDevice(ctx->context.GetRendezvous()); // Knowledge of the implementation of Init (and in-turn // FunctionLibraryRuntime::CreateKernel) tells us that ctx->func_lib_def // will be accessed, so grab on to the lock. // See WARNING comment in Execute (before kernel->Run) - would be nice to // rework to avoid this subtlety. - tensorflow::tf_shared_lock l(ctx->functions_mu); - status->status = - tensorflow::KernelAndDevice::Init(ndef, ctx->func_lib(device), kernel); + tensorflow::tf_shared_lock l(*ctx->context.FunctionsMu()); + status->status = tensorflow::KernelAndDevice::Init( + ndef, ctx->context.func_lib(device), kernel); if (!status->status.ok()) { delete kernel; return; @@ -1136,7 +1083,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // Update output_dtypes inside `kernel`. const tensorflow::OpDef* op_def = nullptr; const tensorflow::FunctionDef* function_def = - ctx->func_lib_def.Find(ndef.op()); + ctx->context.FuncLibDef()->Find(ndef.op()); if (function_def != nullptr) { op_def = &(function_def->signature()); } @@ -1152,8 +1099,7 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, if (!status->status.ok()) { return; } - tensorflow::mutex_lock ml(ctx->cache_mu); - tensorflow::gtl::InsertOrUpdate(&(ctx->kernel_cache), cache_key, kernel); + ctx->context.AddKernelToCache(cache_key, kernel); } const tensorflow::DataTypeVector& output_dtypes = kernel->output_dtypes(); const int output_dtypes_size = output_dtypes.size(); @@ -1171,11 +1117,11 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, // device from the one requested above. device = kernel->device(); } - status->status = ValidateInputTypeAndPlacement(ctx, ctx->devices[0], device, - op, kernel->kernel()); + status->status = ValidateInputTypeAndPlacement(ctx, ctx->context.HostCPU(), + device, op, kernel->kernel()); if (!status->status.ok()) return; std::unique_ptr maybe_stats; - if (ctx->should_store_metadata.load()) { + if (ctx->context.ShouldStoreMetadata()) { maybe_stats.reset(new tensorflow::NodeExecStats); maybe_stats->set_node_name(op->name); maybe_stats->set_all_start_micros(tensorflow::Env::Default()->NowMicros()); @@ -1183,14 +1129,14 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, maybe_stats->set_scheduled_micros(tensorflow::Env::Default()->NowMicros()); // TODO(apassos) track referenced tensors } - if (ctx->Async()) { + if (ctx->context.Async()) { // Note that for async mode, execution order will make sure that all // input handles are ready before executing them. // TODO(agarwal): Consider executing "cheap" kernels inline for performance. tensorflow::EagerNode* node = new ExecuteNode(op, kernel, maybe_stats.release(), output_dtypes, retvals, *num_retvals); - ctx->executor.Add(node); + ctx->context.ExecutorAdd(node); } else { // Execute checks if retvals[i] is nullptr or not to figure if it needs to // allocate it. @@ -1206,23 +1152,24 @@ TFE_TensorHandle* TFE_TensorHandleCopyToDevice(TFE_TensorHandle* h, TFE_Context* ctx, const char* device_name, TF_Status* status) { - status->status = ctx->executor.status(); + status->status = ctx->context.GetStatus(); if (!status->status.ok()) { return nullptr; } - tensorflow::Device* dstd = ctx->devices[0]; + tensorflow::Device* dstd = ctx->context.HostCPU(); if (device_name != nullptr && strlen(device_name) > 0) { - status->status = ctx->device_manager->LookupDevice(device_name, &dstd); + status->status = + ctx->context.device_mgr()->LookupDevice(device_name, &dstd); if (!status->status.ok()) return nullptr; } - if (ctx->Async()) { + if (ctx->context.Async()) { // Note that `h` may not be currently ready. However execution order will // make sure that `h` is ready before the copy is actually done. CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); TFE_TensorHandle* output = node->dst(); // Note that calling Add makes `node` accessible by the EagerExecutor // thread. So further accesses need to be thread-safe. - ctx->executor.Add(node); + ctx->context.ExecutorAdd(node); return output; } else { TFE_TensorHandle* output = nullptr; @@ -1240,24 +1187,20 @@ void TFE_ContextAddFunctionDef(TFE_Context* ctx, tensorflow::errors::InvalidArgument("Invalid FunctionDef proto"); return; } - tensorflow::mutex_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.AddFunctionDef(function_def); + status->status = ctx->context.AddFunctionDef(function_def); } void TFE_ContextAddFunction(TFE_Context* ctx, TF_Function* function, TF_Status* status) { - tensorflow::mutex_lock l(ctx->functions_mu); - status->status = ctx->func_lib_def.AddFunctionDef(function->fdef); + status->status = ctx->context.AddFunctionDef(function->fdef); } void TFE_ContextEnableRunMetadata(TFE_Context* ctx) { - ctx->should_store_metadata.store(true); + ctx->context.SetShouldStoreMetadata(true); } void TFE_ContextDisableRunMetadata(TFE_Context* ctx) { - tensorflow::mutex_lock ml(ctx->metadata_mu); - ctx->should_store_metadata.store(false); - ctx->run_metadata.Clear(); + ctx->context.SetShouldStoreMetadata(false); } } // extern "C" @@ -1286,9 +1229,9 @@ void TFE_ContextExportRunMetadata(TFE_Context* ctx, TF_Buffer* buf, TF_Status* status) { TFE_ContextAsyncWait(ctx, status); if (!status->status.ok()) return; - tensorflow::mutex_lock ml(ctx->metadata_mu); - status->status = MessageToBuffer(ctx->run_metadata, buf); - ctx->run_metadata.Clear(); + tensorflow::mutex_lock ml(*ctx->context.MetadataMu()); + status->status = MessageToBuffer(*ctx->context.RunMetadataProto(), buf); + ctx->context.RunMetadataProto()->Clear(); } namespace { @@ -1363,11 +1306,6 @@ void SetOpAttrValueScalar(TFE_Context* ctx, TFE_Op* op, } // namespace tensorflow -bool TFE_Context::Async() const { - tensorflow::mutex_lock l(async_map_mu); - return tensorflow::gtl::FindWithDefault( - thread_local_async, std::this_thread::get_id(), async_default); -} bool TFE_TensorHandle::IsReady() { if (node_id == 0) return true; @@ -1381,7 +1319,7 @@ tensorflow::Status TFE_TensorHandle::WaitReady() { { tensorflow::mutex_lock l(ctx_mutex_); if (ctx_ == nullptr) return tensorflow::Status::OK(); - executor = &ctx_->executor; + executor = ctx_->context.Executor(); } return executor->WaitFor(node_id); } diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index a79f8ddd33..5b29120b40 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/c/c_api_internal.h" #include "tensorflow/c/eager/runtime.h" #include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/context.h" #include "tensorflow/core/common_runtime/eager/eager_executor.h" #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" #include "tensorflow/core/common_runtime/function.h" @@ -52,85 +53,18 @@ struct TFE_ContextOptions { TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32}; }; -TFE_ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, TFE_ContextDevicePlacementPolicy original_policy); - struct TFE_Context { - explicit TFE_Context(const TFE_ContextOptions& opts, + explicit TFE_Context(const tensorflow::SessionOptions& opts, + TFE_ContextDevicePlacementPolicy default_policy, + bool async, std::unique_ptr device_mgr, tensorflow::Rendezvous* rendezvous) - : soft_placement( - opts.session_options.options.config.allow_soft_placement()), - policy(PlacementPolicy(soft_placement, opts.policy)), - device_manager(std::move(device_mgr)), - devices(device_manager->ListDevices()), - rendezvous(rendezvous), - pflr(new tensorflow::ProcessFunctionLibraryRuntime( - device_manager.get(), opts.session_options.options.env, - TF_GRAPH_DEF_VERSION, &func_lib_def, {})), - log_device_placement( - opts.session_options.options.config.log_device_placement()), - async_default(opts.async) { - if (async_default) executor.EnableAsync(); - - for (auto* device : devices) { - devices_map[tensorflow::StringPiece(device->name())] = device; - } - } - - const bool soft_placement; - const TFE_ContextDevicePlacementPolicy policy; - - // Note: we cannot use C++11 thread_local here as there is no concept of a - // thread-local-object-local variable in C++11. - tensorflow::mutex policy_map_mu; - std::unordered_map - thread_local_policies GUARDED_BY(policy_map_mu); - - std::unique_ptr device_manager; - // Devices owned by device_manager - std::vector devices; - // All devices are not owned. - tensorflow::gtl::FlatMap - devices_map; - tensorflow::Rendezvous* const rendezvous; - - tensorflow::mutex functions_mu; - tensorflow::FunctionLibraryDefinition func_lib_def GUARDED_BY(functions_mu){ - tensorflow::OpRegistry::Global(), {}}; - - // One FunctionLibraryRuntime per device. - // func_libs[i] is the FunctionLibraryRuntime corresponding to - // session->devices[i]. - const std::unique_ptr pflr; - - tensorflow::mutex cache_mu; - std::unordered_map - kernel_cache GUARDED_BY(cache_mu); - - tensorflow::FunctionLibraryRuntime* func_lib(tensorflow::Device* d) const { - return pflr->GetFLR(d->name()); - } + : context(opts, + static_cast( + default_policy), + async, std::move(device_mgr), rendezvous) {} - // Whether we should compute RunMetadata. - std::atomic should_store_metadata{false}; - tensorflow::mutex metadata_mu; - tensorflow::RunMetadata run_metadata GUARDED_BY(metadata_mu); - const bool log_device_placement; - // EagerExecutor for async execution. - tensorflow::EagerExecutor executor; - - // True if running in asynchronous mode. - bool Async() const; - - // True if the default value for execution mode is async. Note that this value - // can be overridden per thread based on `thread_local_async` overrides. - const bool async_default; - mutable tensorflow::mutex async_map_mu; - std::unordered_map thread_local_async - GUARDED_BY(async_map_mu); + tensorflow::EagerContext context; }; struct TFE_TensorHandle : public tensorflow::core::RefCounted { diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index 8ba560bef8..de10b10b7e 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -32,6 +32,28 @@ tf_cuda_library( ], ) +tf_cuda_library( + name = "context", + srcs = [ + "context.cc", + ], + hdrs = [ + "context.h", + ], + visibility = ["//tensorflow:internal"], + deps = [ + ":eager_executor", + ":kernel_and_device", + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", + ], +) + tf_cuda_library( name = "kernel_and_device", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc new file mode 100644 index 0000000000..0566329f18 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/context.cc @@ -0,0 +1,153 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/common_runtime/eager/context.h" + +namespace tensorflow { + +ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, ContextDevicePlacementPolicy original_policy) { + if (!soft_placement) { + return original_policy; + } + if (original_policy == DEVICE_PLACEMENT_EXPLICIT || + original_policy == DEVICE_PLACEMENT_SILENT_FOR_INT32) { + return DEVICE_PLACEMENT_SILENT; + } + return original_policy; +} + +EagerContext::EagerContext(const SessionOptions& opts, + ContextDevicePlacementPolicy default_policy, + bool async, std::unique_ptr device_mgr, + Rendezvous* rendezvous) + : soft_placement_(opts.config.allow_soft_placement()), + policy_(PlacementPolicy(soft_placement_, default_policy)), + device_manager_(std::move(device_mgr)), + devices_(device_manager_->ListDevices()), + rendezvous_(rendezvous), + pflr_(new ProcessFunctionLibraryRuntime(device_manager_.get(), opts.env, + TF_GRAPH_DEF_VERSION, + &func_lib_def_, {})), + log_device_placement_(opts.config.log_device_placement()), + async_default_(async) { + if (async_default_) { + executor_.EnableAsync(); + } + + for (auto* device : devices_) { + devices_map_[device->name()] = device; + } +} + +bool EagerContext::Async() const { + mutex_lock l(async_map_mu_); + return gtl::FindWithDefault(thread_local_async_, std::this_thread::get_id(), + async_default_); +} + +Status EagerContext::SetAsyncForThread(bool async) { + { + tensorflow::mutex_lock l(async_map_mu_); + thread_local_async_[std::this_thread::get_id()] = async; + } + if (async) { + executor_.EnableAsync(); + } else { + // TODO(agarwal): Currently we add a wait here to handle cases where a + // sync op has a control dependency on an async op, and the latter has not + // executed yet. This wait can be removed by storing all the control + // inputs and waiting for them when executing ops. + return executor_.WaitForAllPendingNodes(); + } + return Status::OK(); +} + +void EagerContext::ClearCaches() { + mutex_lock ml(cache_mu_); + gtl::STLDeleteValues(&kernel_cache_); +} + +void EagerContext::SetThreadLocalDevicePlacementPolicy( + ContextDevicePlacementPolicy policy) { + mutex_lock ml(policy_map_mu_); + thread_local_policies_[std::this_thread::get_id()] = policy; +} + +ContextDevicePlacementPolicy EagerContext::GetDevicePlacementPolicy() { + mutex_lock ml(policy_map_mu_); + auto policy_map_it = thread_local_policies_.find(std::this_thread::get_id()); + if (policy_map_it != thread_local_policies_.end()) { + return policy_map_it->second; + } + return policy_; +} + +EagerContext::~EagerContext() { + executor_.WaitForAllPendingNodes().IgnoreError(); + ClearCaches(); + rendezvous_->Unref(); +} + +bool EagerContext::FindFunctionByName(const string& name) { + mutex_lock l(functions_mu_); + return func_lib_def_.Find(name) != nullptr; +} + +Status EagerContext::FindFunctionOpData( + const string& name, const tensorflow::OpRegistrationData** op_data) { + mutex_lock l(functions_mu_); + return func_lib_def_.LookUp(name, op_data); +} + +const FunctionDef* EagerContext::FindFunctionDef(const string& name) { + mutex_lock l(functions_mu_); + return func_lib_def_.Find(name); +} + +Status EagerContext::FindDeviceByName(const string& name, Device** result) { + auto it = devices_map_.find(name); + if (it == devices_map_.end()) { + return errors::InvalidArgument(name, " unknown device."); + } + *result = it->second; + return Status::OK(); +} + +Status EagerContext::AddFunctionDef(const FunctionDef& fdef) { + mutex_lock l(functions_mu_); + return func_lib_def_.AddFunctionDef(fdef); +} + +KernelAndDevice* EagerContext::GetCachedKernel(Fprint128 cache_key) { + tf_shared_lock l(cache_mu_); + return gtl::FindPtrOrNull(kernel_cache_, cache_key); +} + +void EagerContext::AddKernelToCache(Fprint128 cache_key, + KernelAndDevice* kernel) { + mutex_lock ml(cache_mu_); + gtl::InsertOrUpdate(&kernel_cache_, cache_key, kernel); +} + +void EagerContext::SetShouldStoreMetadata(bool value) { + should_store_metadata_.store(value); + if (!value) { + mutex_lock ml(metadata_mu_); + run_metadata_.Clear(); + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h new file mode 100644 index 0000000000..bc97219dae --- /dev/null +++ b/tensorflow/core/common_runtime/eager/context.h @@ -0,0 +1,198 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/eager_executor.h" +#include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/framework/rendezvous.h" +#include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/lib/gtl/stl_util.h" +#include "tensorflow/core/platform/fingerprint.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/platform/thread_annotations.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" + +namespace tensorflow { + +// Note: there's a copy enum in eager/c_api.h. It should be kept in sync. +enum ContextDevicePlacementPolicy { + // Running operations with input tensors on the wrong device will fail. When + // soft placement is enabled acts like TFE_DEVICE_PLACEMENT_SILENT. + DEVICE_PLACEMENT_EXPLICIT = 0, + // Copy the tensor to the right device but log a warning. + DEVICE_PLACEMENT_WARN = 1, + // Silently copy the tensor, which has a performance cost since the + // operation will be blocked till the copy completes. + DEVICE_PLACEMENT_SILENT = 2, + // Default placement policy which silently copies int32 tensors but not other + // dtypes. When soft placement is enabled acts like + // TFE_DEVICE_PLACEMENT_SILENT. + DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, +}; + +ContextDevicePlacementPolicy PlacementPolicy( + bool soft_placement, ContextDevicePlacementPolicy original_policy); + +class EagerContext { + public: + explicit EagerContext(const SessionOptions& opts, + ContextDevicePlacementPolicy default_policy, bool async, + std::unique_ptr device_mgr, + Rendezvous* rendezvous); + + ~EagerContext(); + + // Returns the function library runtime for the given device. + FunctionLibraryRuntime* func_lib(Device* d) const { + return pflr_->GetFLR(d->name()); + } + + // True if running in asynchronous mode. + bool Async() const; + + EagerExecutor* Executor() { return &executor_; } + + // Sets whether this thread should run in synchronous or asynchronous mode. + Status SetAsyncForThread(bool async); + + // TODO(apassos) make this return a constant reference + gtl::FlatMap* device_map() { + return &devices_map_; + } + + // TODO(apassos) make this return a constant reference + std::vector* devices() { return &devices_; } + + // Clears the kernel caches. + void ClearCaches(); + + // Sets the device placement policy for the current thread. + void SetThreadLocalDevicePlacementPolicy(ContextDevicePlacementPolicy policy); + + // Returns the device placement policy for the current thread. + ContextDevicePlacementPolicy GetDevicePlacementPolicy(); + + Status AsyncWait() { return executor_.WaitForAllPendingNodes(); } + + Status GetStatus() { return executor_.status(); } + + void ClearAsyncError() { executor_.ClearError(); } + + bool FindFunctionByName(const string& name); + + Status FindFunctionOpData(const string& name, + const tensorflow::OpRegistrationData** op_data); + + const FunctionDef* FindFunctionDef(const string& name); + + Status FindDeviceByName(const string& name, Device** result); + + Device* HostCPU() { return devices_[0]; } + + bool SoftPlacement() { return soft_placement_; } + + uint64 NextId() { return executor_.NextId(); } + + void ExecutorAdd(EagerNode* node) { executor_.Add(node); } + + Status AddFunctionDef(const FunctionDef& fdef); + + KernelAndDevice* GetCachedKernel(Fprint128 cache_key); + + void AddKernelToCache(Fprint128 cache_key, KernelAndDevice* kernel); + + bool LogDevicePlacement() { return log_device_placement_; } + + Rendezvous* GetRendezvous() { return rendezvous_; } + + mutex* FunctionsMu() { return &functions_mu_; } + + tensorflow::DeviceMgr* device_mgr() { return device_manager_.get(); } + + // TODO(apassos) remove the need for this + void ReleaseDeviceMgr() { device_manager_.release(); } + + // TODO(apassos) clean up RunMetadata storage. + mutex* MetadataMu() { return &metadata_mu_; } + bool ShouldStoreMetadata() { return should_store_metadata_.load(); } + void SetShouldStoreMetadata(bool value); + RunMetadata* RunMetadataProto() { return &run_metadata_; } + + FunctionLibraryDefinition* FuncLibDef() { return &func_lib_def_; } + + private: + const bool soft_placement_; + const ContextDevicePlacementPolicy policy_; + + // Note: we cannot use C++11 thread_local here as there is no concept of a + // thread-local-object-local variable in C++11. + mutex policy_map_mu_; + std::unordered_map + thread_local_policies_ GUARDED_BY(policy_map_mu_); + + std::unique_ptr device_manager_; + // Devices owned by device_manager + std::vector devices_; + // All devices are not owned. + gtl::FlatMap devices_map_; + Rendezvous* const rendezvous_; + + mutex functions_mu_; + FunctionLibraryDefinition func_lib_def_ GUARDED_BY(functions_mu_){ + OpRegistry::Global(), {}}; + + // One FunctionLibraryRuntime per device. + // func_libs[i] is the FunctionLibraryRuntime corresponding to + // session->devices[i]. + const std::unique_ptr pflr_; + + mutex cache_mu_; + std::unordered_map kernel_cache_ + GUARDED_BY(cache_mu_); + + // Whether we should compute RunMetadata. + std::atomic should_store_metadata_{false}; + mutex metadata_mu_; + RunMetadata run_metadata_ GUARDED_BY(metadata_mu_); + const bool log_device_placement_; + // EagerExecutor for async execution. + EagerExecutor executor_; + + // True if the default value for execution mode is async. Note that this value + // can be overridden per thread based on `thread_local_async` overrides. + const bool async_default_; + mutable mutex async_map_mu_; + std::unordered_map thread_local_async_ + GUARDED_BY(async_map_mu_); +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_CONTEXT_H_ -- GitLab From 63d46266ba5b2a513244e13321f76e7acd03aba3 Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Thu, 22 Mar 2018 14:53:59 -0700 Subject: [PATCH 103/906] Move cuDNN RNN ops to core, for use in the internal TF codebase only (not publicly exposed). RELNOTES: Moved cuDNN RNN ops to core. PiperOrigin-RevId: 190130405 --- tensorflow/contrib/BUILD | 2 - tensorflow/contrib/cmake/python_modules.txt | 2 - .../contrib/cmake/tf_core_kernels.cmake | 2 - tensorflow/contrib/cmake/tf_core_ops.cmake | 2 +- tensorflow/contrib/cmake/tf_python.cmake | 3 +- tensorflow/contrib/cudnn_rnn/BUILD | 68 +-------- .../cudnn_rnn/python/ops/cudnn_rnn_ops.py | 7 +- tensorflow/core/BUILD | 47 +++++++ .../api_def/base_api/api_def_CudnnRNN.pbtxt | 36 +++++ .../base_api/api_def_CudnnRNNBackprop.pbtxt | 45 ++++++ .../api_def_CudnnRNNCanonicalToParams.pbtxt | 35 +++++ .../base_api/api_def_CudnnRNNParamsSize.pbtxt | 27 ++++ .../api_def_CudnnRNNParamsToCanonical.pbtxt | 35 +++++ .../api_def/python_api/api_def_CudnnRNN.pbtxt | 4 + .../python_api/api_def_CudnnRNNBackprop.pbtxt | 4 + .../api_def_CudnnRNNCanonicalToParams.pbtxt | 4 + .../api_def_CudnnRNNParamsSize.pbtxt | 4 + .../api_def_CudnnRNNParamsToCanonical.pbtxt | 4 + tensorflow/core/kernels/BUILD | 17 +++ .../kernels/cudnn_rnn_ops.cc | 0 .../cudnn_rnn => core}/ops/cudnn_rnn_ops.cc | 130 ++---------------- .../ops/cudnn_rnn_ops_test.cc | 0 tensorflow/python/BUILD | 8 ++ tensorflow/python/__init__.py | 4 + 24 files changed, 287 insertions(+), 203 deletions(-) create mode 100644 tensorflow/core/api_def/base_api/api_def_CudnnRNN.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_CudnnRNNBackprop.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_CudnnRNNCanonicalToParams.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_CudnnRNNParamsSize.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_CudnnRNNParamsToCanonical.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CudnnRNN.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CudnnRNNBackprop.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CudnnRNNCanonicalToParams.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CudnnRNNParamsSize.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_CudnnRNNParamsToCanonical.pbtxt rename tensorflow/{contrib/cudnn_rnn => core}/kernels/cudnn_rnn_ops.cc (100%) rename tensorflow/{contrib/cudnn_rnn => core}/ops/cudnn_rnn_ops.cc (53%) rename tensorflow/{contrib/cudnn_rnn => core}/ops/cudnn_rnn_ops_test.cc (100%) diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index d103da79e3..2d7bbc016f 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -119,7 +119,6 @@ cc_library( deps = [ "//tensorflow/contrib/boosted_trees:boosted_trees_kernels", "//tensorflow/contrib/coder:all_kernels", - "//tensorflow/contrib/cudnn_rnn:cudnn_rnn_kernels", "//tensorflow/contrib/data/kernels:dataset_kernels", "//tensorflow/contrib/kafka:dataset_kernels", "//tensorflow/contrib/factorization/kernels:all_kernels", @@ -143,7 +142,6 @@ cc_library( deps = [ "//tensorflow/contrib/boosted_trees:boosted_trees_ops_op_lib", "//tensorflow/contrib/coder:all_ops", - "//tensorflow/contrib/cudnn_rnn:cudnn_rnn_ops_op_lib", "//tensorflow/contrib/data:dataset_ops_op_lib", "//tensorflow/contrib/factorization:all_ops", "//tensorflow/contrib/framework:all_ops", diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index 0d2a6a23db..f7d3c73b2c 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -147,8 +147,6 @@ tensorflow/contrib/crf tensorflow/contrib/crf/python tensorflow/contrib/crf/python/ops tensorflow/contrib/cudnn_rnn -tensorflow/contrib/cudnn_rnn/kernels -tensorflow/contrib/cudnn_rnn/ops tensorflow/contrib/cudnn_rnn/python tensorflow/contrib/cudnn_rnn/python/layers tensorflow/contrib/cudnn_rnn/python/ops diff --git a/tensorflow/contrib/cmake/tf_core_kernels.cmake b/tensorflow/contrib/cmake/tf_core_kernels.cmake index 998f99ecc1..ed018b4fed 100644 --- a/tensorflow/contrib/cmake/tf_core_kernels.cmake +++ b/tensorflow/contrib/cmake/tf_core_kernels.cmake @@ -67,8 +67,6 @@ if(tensorflow_BUILD_CONTRIB_KERNELS) "${tensorflow_source_dir}/tensorflow/contrib/coder/kernels/range_coder_ops.cc" "${tensorflow_source_dir}/tensorflow/contrib/coder/kernels/range_coder_ops_util.cc" "${tensorflow_source_dir}/tensorflow/contrib/coder/ops/coder_ops.cc" - "${tensorflow_source_dir}/tensorflow/contrib/cudnn_rnn/kernels/cudnn_rnn_ops.cc" - "${tensorflow_source_dir}/tensorflow/contrib/cudnn_rnn/ops/cudnn_rnn_ops.cc" "${tensorflow_source_dir}/tensorflow/contrib/data/kernels/ignore_errors_dataset_op.cc" "${tensorflow_source_dir}/tensorflow/contrib/data/kernels/prefetching_kernels.cc" "${tensorflow_source_dir}/tensorflow/contrib/data/kernels/threadpool_dataset_op.cc" diff --git a/tensorflow/contrib/cmake/tf_core_ops.cmake b/tensorflow/contrib/cmake/tf_core_ops.cmake index 59e094812a..d6712aa2b4 100644 --- a/tensorflow/contrib/cmake/tf_core_ops.cmake +++ b/tensorflow/contrib/cmake/tf_core_ops.cmake @@ -21,6 +21,7 @@ set(tf_op_lib_names "checkpoint_ops" "control_flow_ops" "ctc_ops" + "cudnn_rnn_ops" "data_flow_ops" "dataset_ops" "functional_ops" @@ -84,7 +85,6 @@ GENERATE_CONTRIB_OP_LIBRARY(boosted_trees_prediction "${tensorflow_source_dir}/t GENERATE_CONTRIB_OP_LIBRARY(boosted_trees_quantiles "${tensorflow_source_dir}/tensorflow/contrib/boosted_trees/ops/quantile_ops.cc") GENERATE_CONTRIB_OP_LIBRARY(boosted_trees_stats_accumulator "${tensorflow_source_dir}/tensorflow/contrib/boosted_trees/ops/stats_accumulator_ops.cc") GENERATE_CONTRIB_OP_LIBRARY(coder "${tensorflow_source_dir}/tensorflow/contrib/coder/ops/coder_ops.cc") -GENERATE_CONTRIB_OP_LIBRARY(cudnn_rnn "${tensorflow_source_dir}/tensorflow/contrib/cudnn_rnn/ops/cudnn_rnn_ops.cc") GENERATE_CONTRIB_OP_LIBRARY(data_dataset "${tensorflow_source_dir}/tensorflow/contrib/data/ops/dataset_ops.cc") GENERATE_CONTRIB_OP_LIBRARY(factorization_clustering "${tensorflow_source_dir}/tensorflow/contrib/factorization/ops/clustering_ops.cc") GENERATE_CONTRIB_OP_LIBRARY(factorization_factorization "${tensorflow_source_dir}/tensorflow/contrib/factorization/ops/factorization_ops.cc") diff --git a/tensorflow/contrib/cmake/tf_python.cmake b/tensorflow/contrib/cmake/tf_python.cmake index 1e354bf212..31e715b654 100755 --- a/tensorflow/contrib/cmake/tf_python.cmake +++ b/tensorflow/contrib/cmake/tf_python.cmake @@ -326,6 +326,7 @@ GENERATE_PYTHON_OP_LIB("checkpoint_ops") GENERATE_PYTHON_OP_LIB("control_flow_ops" ADDITIONAL_LIBRARIES $) GENERATE_PYTHON_OP_LIB("ctc_ops") +GENERATE_PYTHON_OP_LIB("cudnn_rnn_ops") GENERATE_PYTHON_OP_LIB("data_flow_ops") GENERATE_PYTHON_OP_LIB("dataset_ops") GENERATE_PYTHON_OP_LIB("image_ops") @@ -367,8 +368,6 @@ GENERATE_PYTHON_OP_LIB("contrib_boosted_trees_stats_accumulator_ops" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/contrib/boosted_trees/python/ops/gen_stats_accumulator_ops.py) GENERATE_PYTHON_OP_LIB("contrib_coder_ops" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/contrib/coder/python/ops/gen_coder_ops.py) -GENERATE_PYTHON_OP_LIB("contrib_cudnn_rnn_ops" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/contrib/cudnn_rnn/ops/gen_cudnn_rnn_ops.py) GENERATE_PYTHON_OP_LIB("contrib_data_dataset_ops" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/tf_python/tensorflow/contrib/data/python/ops/gen_dataset_ops.py) GENERATE_PYTHON_OP_LIB("contrib_factorization_clustering_ops" diff --git a/tensorflow/contrib/cudnn_rnn/BUILD b/tensorflow/contrib/cudnn_rnn/BUILD index fec358c4e1..fa86ad38c9 100644 --- a/tensorflow/contrib/cudnn_rnn/BUILD +++ b/tensorflow/contrib/cudnn_rnn/BUILD @@ -9,52 +9,10 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -load("//tensorflow:tensorflow.bzl", "tf_custom_op_library") load("//tensorflow:tensorflow.bzl", "tf_gen_op_libs") load("//tensorflow:tensorflow.bzl", "tf_gen_op_wrapper_py") -load("//tensorflow:tensorflow.bzl", "tf_kernel_library") load("//tensorflow:tensorflow.bzl", "cuda_py_test") load("//tensorflow:tensorflow.bzl", "tf_custom_op_py_library") -load("//tensorflow:tensorflow.bzl", "tf_cc_test") - -tf_custom_op_library( - name = "python/ops/_cudnn_rnn_ops.so", - srcs = [ - "kernels/cudnn_rnn_ops.cc", - "ops/cudnn_rnn_ops.cc", - ], - deps = [ - "//tensorflow/core/kernels:bounds_check_lib", - "@farmhash_archive//:farmhash", - ], -) - -tf_kernel_library( - name = "cudnn_rnn_kernels", - srcs = ["kernels/cudnn_rnn_ops.cc"], - visibility = ["//visibility:public"], - deps = [ - "//tensorflow/core:framework", - "//tensorflow/core:lib", - "//tensorflow/core:lib_internal", - "//tensorflow/core:stream_executor", - "//tensorflow/core/kernels:bounds_check_lib", - "//third_party/eigen3", - "@farmhash_archive//:farmhash", - ], -) - -tf_gen_op_libs( - op_lib_names = ["cudnn_rnn_ops"], - deps = [ - "//tensorflow/core:lib", - ], -) - -tf_gen_op_wrapper_py( - name = "cudnn_rnn_ops", - deps = [":cudnn_rnn_ops_op_lib"], -) tf_custom_op_py_library( name = "cudnn_rnn_py", @@ -64,20 +22,13 @@ tf_custom_op_py_library( "python/layers/cudnn_rnn.py", "python/ops/cudnn_rnn_ops.py", ], - dso = [ - ":python/ops/_cudnn_rnn_ops.so", - ], - kernels = [ - ":cudnn_rnn_kernels", - ":cudnn_rnn_ops_op_lib", - ], srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ - ":cudnn_rnn_ops", "//tensorflow/contrib/util:util_py", "//tensorflow/python:array_ops", "//tensorflow/python:control_flow_ops", + "//tensorflow/python:cudnn_rnn_ops_gen", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:init_ops", @@ -173,23 +124,6 @@ cuda_py_test( ], ) -tf_cc_test( - name = "cudnn_rnn_ops_test_cc", - size = "small", - srcs = [ - "ops/cudnn_rnn_ops_test.cc", - ], - deps = [ - ":cudnn_rnn_ops_op_lib", - "//tensorflow/core", - "//tensorflow/core:framework", - "//tensorflow/core:lib", - "//tensorflow/core:test", - "//tensorflow/core:test_main", - "//tensorflow/core:testlib", - ], -) - filegroup( name = "all_files", srcs = glob( diff --git a/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py b/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py index e87162f0ee..622241a177 100644 --- a/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py +++ b/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py @@ -17,27 +17,22 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.cudnn_rnn.ops import gen_cudnn_rnn_ops from tensorflow.contrib.rnn.python.ops import lstm_ops -from tensorflow.contrib.util import loader from tensorflow.python.framework import common_shapes from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import random_seed from tensorflow.python.layers import base as base_layer from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_cudnn_rnn_ops from tensorflow.python.ops import init_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn_ops from tensorflow.python.ops import rnn_cell_impl from tensorflow.python.ops import state_ops from tensorflow.python.ops import variable_scope as vs -from tensorflow.python.platform import resource_loader from tensorflow.python.training import saver -_cudnn_rnn_ops_so = loader.load_op_library( - resource_loader.get_path_to_datafile("_cudnn_rnn_ops.so")) - CUDNN_RNN_UNIDIRECTION = "unidirectional" CUDNN_RNN_BIDIRECTION = "bidirectional" CUDNN_LSTM = "lstm" diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 15cbba8285..2885a9f823 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -688,6 +688,34 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "cudnn_rnn_ops", + srcs = [ + "ops/cudnn_rnn_ops.cc", + ], + linkstatic = 1, + visibility = ["//tensorflow:internal"], + deps = [ + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:stream_executor", + "//tensorflow/core/kernels:bounds_check_lib", + "//third_party/eigen3", + "@farmhash_archive//:farmhash", + ], + alwayslink = 1, +) + +tf_gen_op_libs( + op_lib_names = [ + "cudnn_rnn_ops", + ], + deps = [ + ":lib", + ], +) + cc_library( name = "ops", visibility = ["//visibility:public"], @@ -700,6 +728,7 @@ cc_library( ":checkpoint_ops_op_lib", ":control_flow_ops_op_lib", ":ctc_ops_op_lib", + ":cudnn_rnn_ops_op_lib", ":data_flow_ops_op_lib", ":dataset_ops_op_lib", ":function_ops_op_lib", @@ -840,6 +869,7 @@ cc_library( "//tensorflow/core/kernels:checkpoint_ops", "//tensorflow/core/kernels:control_flow_ops", "//tensorflow/core/kernels:ctc_ops", + "//tensorflow/core/kernels:cudnn_rnn_kernels", "//tensorflow/core/kernels:data_flow", "//tensorflow/core/kernels:dataset_ops", "//tensorflow/core/kernels:fake_quant_ops", @@ -2914,6 +2944,23 @@ tf_cc_tests( ], ) +tf_cc_test( + name = "cudnn_rnn_ops_test_cc", + size = "small", + srcs = [ + "ops/cudnn_rnn_ops_test.cc", + ], + deps = [ + ":cudnn_rnn_ops", + "//tensorflow/core", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ], +) + tf_cc_test_mkl( name = "mkl_runtime_tests", size = "small", diff --git a/tensorflow/core/api_def/base_api/api_def_CudnnRNN.pbtxt b/tensorflow/core/api_def/base_api/api_def_CudnnRNN.pbtxt new file mode 100644 index 0000000000..daeb5fe9a2 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_CudnnRNN.pbtxt @@ -0,0 +1,36 @@ +op { + graph_op_name: "CudnnRNN" + summary: "A RNN backed by cuDNN." + description: <
- + - + - + - + - +
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.7.0rc1CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc1GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
Label Prediction
5.9 3.0 4.3 1.5 1
5.9 3.0 4.3 1.5 1 1
6.9 3.1 5.4 2.1 2
6.9 3.1 5.4 2.1 2 2
5.1 3.3 1.7 0.5 0
5.1 3.3 1.7 0.5 0 0
6.0 3.4 4.5 1.6 1
6.0 3.4 4.5 1.6 1 2
5.5 2.5 4.0 1.3 1
5.5 2.5 4.0 1.3 1 1
@@ -631,6 +642,10 @@ Test set accuracy: 0.967 An accuracy of 0.967 implies that our trained model correctly classified 29 out of the 30 Iris species in the test set. +To get a deeper understanding of different metrics for evaluating +models, see the +[Classification section of Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course/classification). + ### Predicting @@ -723,7 +738,6 @@ Prediction is "Virginica" (97.9%), expected "Virginica" ## Summary - This document provides a short introduction to machine learning. Because `premade_estimators.py` relies on high-level APIs, much of the diff --git a/tensorflow/docs_src/get_started/index.md b/tensorflow/docs_src/get_started/index.md index b7bd1286e3..fb83a770a5 100644 --- a/tensorflow/docs_src/get_started/index.md +++ b/tensorflow/docs_src/get_started/index.md @@ -1,5 +1,12 @@ # Getting Started +If you are new to machine learning, we recommend taking the following online +course prior to diving into TensorFlow documentation: + + * [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course/), + which introduces machine learning concepts and encourages experimentation + with existing TensorFlow code. + TensorFlow is a tool for machine learning. While it contains a wide range of functionality, TensorFlow is mainly designed for deep neural network models. diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index 8612762271..5e9a84bff6 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -506,11 +506,18 @@ TensorFlow programs:
Hello, TensorFlow!
-If you are new to TensorFlow, see @{$get_started/premade_estimators$Getting Started with TensorFlow}. - If the system outputs an error message instead of a greeting, see [Common installation problems](#common_installation_problems). +If you are new to machine learning, we recommend the following: + +* [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course) +* @{$get_started/get_started_for_beginners$Getting Started for ML Beginners} + +If you are experienced with machine learning but new to TensorFlow, see +@{$get_started/premade_estimators$Getting Started with TensorFlow}. + + ## Common installation problems We are relying on Stack Overflow to document TensorFlow installation problems diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index 7207cb4f2b..55b460e189 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -400,12 +400,18 @@ writing TensorFlow programs:
Hello, TensorFlow!
-If you are new to TensorFlow, see -@{$get_started/premade_estimators$Getting Started with TensorFlow}. - If the system outputs an error message instead of a greeting, see [Common installation problems](#common_installation_problems). +If you are new to machine learning, we recommend the following: + +* [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course) +* @{$get_started/get_started_for_beginners$Getting Started for ML Beginners} + +If you are experienced with machine learning but new to TensorFlow, see +@{$get_started/premade_estimators$Getting Started with TensorFlow}. + + ## Common installation problems We are relying on Stack Overflow to document TensorFlow installation problems diff --git a/tensorflow/docs_src/install/install_windows.md b/tensorflow/docs_src/install/install_windows.md index 2413bc9cfb..86add74da1 100644 --- a/tensorflow/docs_src/install/install_windows.md +++ b/tensorflow/docs_src/install/install_windows.md @@ -17,7 +17,7 @@ You must choose one of the following types of TensorFlow to install: NVIDIA® GPU, you must install this version. Note that this version of TensorFlow is typically much easier to install (typically, in 5 or 10 minutes), so even if you have an NVIDIA GPU, we recommend - installing this version first. Prebuilt binaries will use AVX instructions. + installing this version first. Prebuilt binaries will use AVX instructions. * **TensorFlow with GPU support**. TensorFlow programs typically run significantly faster on a GPU than on a CPU. Therefore, if your system has a NVIDIA® GPU meeting the prerequisites shown below @@ -154,13 +154,17 @@ TensorFlow programs:
Hello, TensorFlow!
-If you are new to TensorFlow, see @{$get_started/premade_estimators$Getting Started with TensorFlow}. - If the system outputs an error message instead of a greeting, see [Common installation problems](#common_installation_problems). -There is also a helpful [script](https://gist.github.com/mrry/ee5dbcfdd045fa48a27d56664411d41c) -for Windows TensorFlow installation issues. +If you are new to machine learning, we recommend the following: + +* [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course) +* @{$get_started/get_started_for_beginners$Getting Started for ML Beginners} + +If you are experienced with machine learning but new to TensorFlow, see +@{$get_started/premade_estimators$Getting Started with TensorFlow}. + ## Common installation problems diff --git a/tensorflow/docs_src/programmers_guide/embedding.md b/tensorflow/docs_src/programmers_guide/embedding.md index e8027fc12b..d5703e0737 100644 --- a/tensorflow/docs_src/programmers_guide/embedding.md +++ b/tensorflow/docs_src/programmers_guide/embedding.md @@ -7,6 +7,9 @@ with the TensorBoard Embedding Projector newcomers to machine learning or TensorFlow, and the Embedding Projector how-to is for users at all levels. +An alternative tutorial on these concepts is available in the +[Embeddings section of Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course/embeddings/video-lecture). + [TOC] An **embedding** is a mapping from discrete objects, such as words, to vectors -- GitLab From cf5729fc4710c9e579afa7c1176b00c9c0acec6e Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Fri, 23 Mar 2018 13:20:13 -0700 Subject: [PATCH 166/906] Updates reduce_mean op. PiperOrigin-RevId: 190264873 --- tensorflow/contrib/lite/testing/generate_examples.py | 6 +++--- tensorflow/contrib/lite/toco/import_tensorflow.cc | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py index 38de9dcf2c..e4ef17585f 100644 --- a/tensorflow/contrib/lite/testing/generate_examples.py +++ b/tensorflow/contrib/lite/testing/generate_examples.py @@ -754,7 +754,7 @@ def make_mean_tests(zip_path): [-1, -2, -3], [0, 0, 0], [2, 2, 0], [1, 0, -3, -3] ], "const_axis": [True, False], - "keep_dims": [True, False], + "keepdims": [True, False], }, { "input_dtype": [tf.float32, tf.int32, tf.int64], "input_shape": [[1, 224, 224, 3]], @@ -765,7 +765,7 @@ def make_mean_tests(zip_path): [2, 2, 3], [-3, -3, -4], [-3, 2, 1] ], "const_axis": [True, False], - "keep_dims": [True, False], + "keepdims": [True, False], }] def build_graph(parameters): @@ -788,7 +788,7 @@ def make_mean_tests(zip_path): input_tensors = [input_tensor, axis] out = tf.reduce_mean( - input_tensor, axis=axis, keep_dims=parameters["keep_dims"]) + input_tensor, axis=axis, keepdims=parameters["keepdims"]) return input_tensors, [out] def build_inputs(parameters, sess, inputs, outputs): diff --git a/tensorflow/contrib/lite/toco/import_tensorflow.cc b/tensorflow/contrib/lite/toco/import_tensorflow.cc index a7a50e6fc9..b844e0b948 100644 --- a/tensorflow/contrib/lite/toco/import_tensorflow.cc +++ b/tensorflow/contrib/lite/toco/import_tensorflow.cc @@ -1541,7 +1541,9 @@ void ConvertMeanOperator(const NodeDef& node, op->inputs.push_back(node.input(1)); op->outputs.push_back(node.name()); model->operators.emplace_back(op); - if (HasAttr(node, "keep_dims")) { + if (HasAttr(node, "keepdims")) { + op->keep_dims = GetBoolAttr(node, "keepdims"); + } else if (HasAttr(node, "keep_dims")) { op->keep_dims = GetBoolAttr(node, "keep_dims"); } } -- GitLab From 9b9ca14ae9720b7c28351191a9d9529fc68884b1 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Fri, 23 Mar 2018 13:23:35 -0700 Subject: [PATCH 167/906] Moves TensorHandle to common_runtime PiperOrigin-RevId: 190265301 --- tensorflow/c/eager/BUILD | 2 + tensorflow/c/eager/c_api.cc | 272 ++++++++---------- tensorflow/c/eager/c_api_internal.h | 85 +----- tensorflow/core/common_runtime/eager/BUILD | 23 ++ .../common_runtime/eager/tensor_handle.cc | 107 +++++++ .../core/common_runtime/eager/tensor_handle.h | 130 +++++++++ tensorflow/python/eager/pywrap_tfe_src.cc | 7 +- tensorflow/python/lib/core/py_func.cc | 2 +- 8 files changed, 393 insertions(+), 235 deletions(-) create mode 100644 tensorflow/core/common_runtime/eager/tensor_handle.cc create mode 100644 tensorflow/core/common_runtime/eager/tensor_handle.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index bea5a121b3..d2d8d59323 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -31,6 +31,7 @@ tf_cuda_library( "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", + "//tensorflow/core/common_runtime/eager:tensor_handle", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", "//tensorflow/core:framework_internal", @@ -68,6 +69,7 @@ tf_cuda_library( "//tensorflow/core/common_runtime/eager:context", "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", + "//tensorflow/core/common_runtime/eager:tensor_handle", ], ) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 2402a6d044..59432f2ef8 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -161,29 +161,32 @@ TFE_TensorHandle* TFE_NewTensorHandle(TF_Tensor* t, TF_Status* status) { void TFE_DeleteTensorHandle(TFE_TensorHandle* h) { DCHECK(h); - h->Unref(); + if (h->handle) { + h->handle->Unref(); + } + delete h; } TF_DataType TFE_TensorHandleDataType(TFE_TensorHandle* h) { - return static_cast(h->dtype); + return static_cast(h->handle->dtype); } int TFE_TensorHandleNumDims(TFE_TensorHandle* h, TF_Status* status) { const tensorflow::Tensor* t = nullptr; - status->status = h->Tensor(&t); + status->status = h->handle->Tensor(&t); return t == nullptr ? 0 : t->dims(); } int64_t TFE_TensorHandleDim(TFE_TensorHandle* h, int dim_index, TF_Status* status) { const tensorflow::Tensor* t = nullptr; - status->status = h->Tensor(&t); + status->status = h->handle->Tensor(&t); return t == nullptr ? 0 : t->dim_size(dim_index); } const char* TFE_TensorHandleDeviceName(TFE_TensorHandle* h, TF_Status* status) { tensorflow::Device* d = nullptr; - status->status = h->OpDevice(&d); + status->status = h->handle->OpDevice(&d); return (d == nullptr) ? "/job:localhost/replica:0/task:0/device:CPU:0" : d->name().c_str(); } @@ -193,7 +196,7 @@ TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status) { tensorflow::Device* d = nullptr; tensorflow::Device* op_device = nullptr; const tensorflow::Tensor* t = nullptr; - status->status = h->TensorAndDevice(&t, &d, &op_device); + status->status = h->handle->TensorAndDevice(&t, &d, &op_device); if (!status->status.ok()) return nullptr; if (!IsCPU(d)) { TF_SetStatus(status, TF_UNIMPLEMENTED, @@ -212,10 +215,10 @@ TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status) { namespace { -tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, +tensorflow::Status TensorHandleCopyToDevice(tensorflow::TensorHandle* h, TFE_Context* ctx, tensorflow::Device* dstd, - TFE_TensorHandle** output) { + tensorflow::TensorHandle** output) { const tensorflow::Tensor* src = nullptr; tensorflow::Device* srcd = nullptr; // TODO(agarwal): src_opd is unused. Perhaps allow TensorAndDevice to accept @@ -232,7 +235,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, const bool both_on_cpu = src_cpu && dst_cpu; if (is_same_device || both_on_cpu) { dstd = dst_cpu ? nullptr : dstd; - *output = new TFE_TensorHandle(*src, dstd, dstd); + *output = new tensorflow::TensorHandle(*src, dstd, dstd); return tensorflow::Status::OK(); } if (!dst_cpu && (src->dtype() != tensorflow::DT_VARIANT && @@ -249,7 +252,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, tensorflow::Tensor dst(dstd->GetAllocator(attr), src->dtype(), src->shape()); if (src->shape().num_elements() == 0) { dstd = dst_cpu ? nullptr : dstd; - *output = new TFE_TensorHandle(dst, dstd, dstd); + *output = new tensorflow::TensorHandle(dst, dstd, dstd); return tensorflow::Status::OK(); } tensorflow::DeviceContext* src_device_context = nullptr; @@ -280,7 +283,7 @@ tensorflow::Status TensorHandleCopyToDevice(TFE_TensorHandle* h, n.WaitForNotification(); if (status.ok()) { dstd = dst_cpu ? nullptr : dstd; - *output = new TFE_TensorHandle(dst, dstd, dstd); + *output = new tensorflow::TensorHandle(dst, dstd, dstd); } return status; } @@ -335,12 +338,12 @@ void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* h, TF_Status* status) { tensorflow::Device* d = nullptr; // TODO(agarwal): This call may block if h is not ready. Avoid this if // possible. - status->status = h->Device(&d); + status->status = h->handle->Device(&d); if (!status->status.ok()) return; if (!IsCPU(d)) op->device = d; } - h->Ref(); - op->inputs.push_back(h); + h->handle->Ref(); + op->inputs.push_back(h->handle); op->attrs.NumInputs(op->inputs.size()); } @@ -506,6 +509,79 @@ void TFE_OpSetAttrFunctionList(TFE_Op* op, const char* attr_name, namespace { +class CopyToDeviceNode : public tensorflow::EagerNode { + public: + CopyToDeviceNode(tensorflow::TensorHandle* src, tensorflow::Device* dstd, + TFE_Context* ctx) + : tensorflow::EagerNode(ctx->context.NextId()), + src_(src), + dstd_(dstd), + ctx_(ctx), + dst_(new tensorflow::TensorHandle(id, src_->dtype, &ctx->context)) { + src_->Ref(); + dst_->Ref(); + } + + ~CopyToDeviceNode() override { + src_->Unref(); + dst_->Unref(); + } + + tensorflow::Status Run() override { + tensorflow::TensorHandle* temp = nullptr; + TF_RETURN_IF_ERROR(TensorHandleCopyToDevice(src_, ctx_, dstd_, &temp)); + const tensorflow::Tensor* tensor = nullptr; + tensorflow::Device* device = nullptr; + tensorflow::Device* op_device = nullptr; + tensorflow::Status status = + temp->TensorAndDevice(&tensor, &device, &op_device); + // `temp` is a ready handle. So the following call should return OK. + TF_DCHECK_OK(status) << status.error_message(); + DCHECK(tensor); + dst_->SetTensorAndDevice(*tensor, device, op_device); + temp->Unref(); + return tensorflow::Status::OK(); + } + + tensorflow::TensorHandle* dst() { return dst_; } + + private: + tensorflow::TensorHandle* src_; + tensorflow::Device* dstd_; + TFE_Context* ctx_; + tensorflow::TensorHandle* dst_; +}; + +// TODO(apassos) move to TensorHandle +tensorflow::TensorHandle* TFE_TensorHandleCopyToDevice_Internal( + tensorflow::TensorHandle* h, TFE_Context* ctx, const char* device_name, + TF_Status* status) { + status->status = ctx->context.GetStatus(); + if (!status->status.ok()) { + return nullptr; + } + tensorflow::Device* dstd = ctx->context.HostCPU(); + if (device_name != nullptr && strlen(device_name) > 0) { + status->status = + ctx->context.device_mgr()->LookupDevice(device_name, &dstd); + if (!status->status.ok()) return nullptr; + } + if (ctx->context.Async()) { + // Note that `h` may not be currently ready. However execution order will + // make sure that `h` is ready before the copy is actually done. + CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); + tensorflow::TensorHandle* output = node->dst(); + // Note that calling Add makes `node` accessible by the EagerExecutor + // thread. So further accesses need to be thread-safe. + ctx->context.ExecutorAdd(node); + return output; + } else { + tensorflow::TensorHandle* output = nullptr; + status->status = TensorHandleCopyToDevice(h, ctx, dstd, &output); + return output; + } +} + tensorflow::Status ValidateInputTypeAndPlacement( TFE_Context* ctx, tensorflow::Device* host_device, tensorflow::Device* op_device, TFE_Op* op, @@ -518,7 +594,7 @@ tensorflow::Status ValidateInputTypeAndPlacement( for (int i = 0; i < op->inputs.size(); ++i) { const tensorflow::Device* expected_device = memtypes[i] == tensorflow::HOST_MEMORY ? host_device : op_device; - TFE_TensorHandle* handle = op->inputs[i]; + tensorflow::TensorHandle* handle = op->inputs[i]; tensorflow::Device* handle_device = nullptr; TF_RETURN_IF_ERROR(handle->Device(&handle_device)); const tensorflow::Device* actual_device = @@ -560,8 +636,9 @@ tensorflow::Status ValidateInputTypeAndPlacement( // We are only here if the policy is warn or silent copies, so we should // trigger a copy. TF_Status* s = TF_NewStatus(); - TFE_TensorHandle* copied_tensor = TFE_TensorHandleCopyToDevice( - handle, ctx, expected_device->name().c_str(), s); + tensorflow::TensorHandle* copied_tensor = + TFE_TensorHandleCopyToDevice_Internal( + handle, ctx, expected_device->name().c_str(), s); tensorflow::Status status = s->status; TF_DeleteStatus(s); if (!status.ok()) { @@ -616,9 +693,10 @@ tensorflow::Device* SelectDevice(const tensorflow::NodeDef& ndef, tensorflow::Status Execute( TFE_Context* ctx, tensorflow::Device* device, - const tensorflow::gtl::InlinedVector& op_inputs, + const tensorflow::gtl::InlinedVector& + op_inputs, tensorflow::KernelAndDevice* kernel, tensorflow::NodeExecStats* maybe_stats, - TFE_TensorHandle** retvals, int num_retvals) { + tensorflow::TensorHandle** retvals, int num_retvals) { if (!ctx->context.SoftPlacement() && device == nullptr) { device = ctx->context.HostCPU(); } @@ -683,7 +761,7 @@ tensorflow::Status Execute( d = nullptr; } if (retvals[i] == nullptr) { - retvals[i] = new TFE_TensorHandle(outputs[i], d, op_device); + retvals[i] = new tensorflow::TensorHandle(outputs[i], d, op_device); } else { retvals[i]->SetTensorAndDevice(outputs[i], d, op_device); } @@ -711,9 +789,10 @@ class ExecuteNode : public tensorflow::EagerNode { } TFE_Context* ctx = op->ctx; for (int i = 0; i < num_retvals; ++i) { - TFE_TensorHandle* h = new TFE_TensorHandle(id, output_dtypes[i], ctx); + tensorflow::TensorHandle* h = + new tensorflow::TensorHandle(id, output_dtypes[i], &ctx->context); h->Ref(); - retvals[i] = h; + retvals[i] = new TFE_TensorHandle(h); retvals_[i] = h; } } @@ -745,54 +824,12 @@ class ExecuteNode : public tensorflow::EagerNode { private: TFE_Context* ctx_; tensorflow::Device* op_device_; - tensorflow::gtl::InlinedVector inputs_; + tensorflow::gtl::InlinedVector inputs_; tensorflow::KernelAndDevice* kernel_; std::unique_ptr maybe_stats_; - tensorflow::gtl::InlinedVector retvals_; + tensorflow::gtl::InlinedVector retvals_; }; -class CopyToDeviceNode : public tensorflow::EagerNode { - public: - CopyToDeviceNode(TFE_TensorHandle* src, tensorflow::Device* dstd, - TFE_Context* ctx) - : tensorflow::EagerNode(ctx->context.NextId()), - src_(src), - dstd_(dstd), - ctx_(ctx), - dst_(new TFE_TensorHandle(id, src_->dtype, ctx)) { - src_->Ref(); - dst_->Ref(); - } - - ~CopyToDeviceNode() override { - src_->Unref(); - dst_->Unref(); - } - - tensorflow::Status Run() override { - TFE_TensorHandle* temp = nullptr; - TF_RETURN_IF_ERROR(TensorHandleCopyToDevice(src_, ctx_, dstd_, &temp)); - const tensorflow::Tensor* tensor = nullptr; - tensorflow::Device* device = nullptr; - tensorflow::Device* op_device = nullptr; - tensorflow::Status status = - temp->TensorAndDevice(&tensor, &device, &op_device); - // `temp` is a ready handle. So the following call should return OK. - TF_DCHECK_OK(status) << status.error_message(); - DCHECK(tensor); - dst_->SetTensorAndDevice(*tensor, device, op_device); - temp->Unref(); - return tensorflow::Status::OK(); - } - - TFE_TensorHandle* dst() { return dst_; } - - private: - TFE_TensorHandle* src_; - tensorflow::Device* dstd_; - TFE_Context* ctx_; - TFE_TensorHandle* dst_; -}; #ifdef TENSORFLOW_EAGER_USE_XLA // Synthesizes and returns a wrapper function over `op`, which must be a @@ -1140,11 +1177,14 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, } else { // Execute checks if retvals[i] is nullptr or not to figure if it needs to // allocate it. + std::vector handle_retvals(*num_retvals, + nullptr); + status->status = + Execute(op->ctx, op->device, op->inputs, kernel, maybe_stats.get(), + handle_retvals.data(), *num_retvals); for (int i = 0; i < *num_retvals; ++i) { - retvals[i] = nullptr; + retvals[i] = new TFE_TensorHandle(handle_retvals[i]); } - status->status = Execute(op->ctx, op->device, op->inputs, kernel, - maybe_stats.get(), retvals, *num_retvals); } } @@ -1152,30 +1192,12 @@ TFE_TensorHandle* TFE_TensorHandleCopyToDevice(TFE_TensorHandle* h, TFE_Context* ctx, const char* device_name, TF_Status* status) { - status->status = ctx->context.GetStatus(); - if (!status->status.ok()) { - return nullptr; - } - tensorflow::Device* dstd = ctx->context.HostCPU(); - if (device_name != nullptr && strlen(device_name) > 0) { - status->status = - ctx->context.device_mgr()->LookupDevice(device_name, &dstd); - if (!status->status.ok()) return nullptr; - } - if (ctx->context.Async()) { - // Note that `h` may not be currently ready. However execution order will - // make sure that `h` is ready before the copy is actually done. - CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); - TFE_TensorHandle* output = node->dst(); - // Note that calling Add makes `node` accessible by the EagerExecutor - // thread. So further accesses need to be thread-safe. - ctx->context.ExecutorAdd(node); - return output; - } else { - TFE_TensorHandle* output = nullptr; - status->status = TensorHandleCopyToDevice(h, ctx, dstd, &output); - return output; + tensorflow::TensorHandle* handle = TFE_TensorHandleCopyToDevice_Internal( + h->handle, ctx, device_name, status); + if (status->status.ok()) { + return new TFE_TensorHandle(handle); } + return nullptr; } void TFE_ContextAddFunctionDef(TFE_Context* ctx, @@ -1214,7 +1236,7 @@ const tensorflow::Tensor* TFE_TensorHandleUnderlyingTensorInHostMemory( tensorflow::Device* d = nullptr; tensorflow::Device* op_device = nullptr; const tensorflow::Tensor* t = nullptr; - status->status = h->TensorAndDevice(&t, &d, &op_device); + status->status = h->handle->TensorAndDevice(&t, &d, &op_device); if (!status->status.ok()) return nullptr; if (d != nullptr) { status->status = tensorflow::errors::FailedPrecondition( @@ -1306,70 +1328,8 @@ void SetOpAttrValueScalar(TFE_Context* ctx, TFE_Op* op, } // namespace tensorflow - -bool TFE_TensorHandle::IsReady() { - if (node_id == 0) return true; - tensorflow::mutex_lock l(ctx_mutex_); - return ctx_ == nullptr; -} - -tensorflow::Status TFE_TensorHandle::WaitReady() { - if (node_id == 0) return tensorflow::Status::OK(); - tensorflow::EagerExecutor* executor = nullptr; - { - tensorflow::mutex_lock l(ctx_mutex_); - if (ctx_ == nullptr) return tensorflow::Status::OK(); - executor = ctx_->context.Executor(); - } - return executor->WaitFor(node_id); -} - -tensorflow::Status TFE_TensorHandle::Tensor(const tensorflow::Tensor** t) { - TF_RETURN_IF_ERROR(WaitReady()); - DCHECK(IsReady()); - *t = &tensor_; - return tensorflow::Status::OK(); -} - -tensorflow::Status TFE_TensorHandle::Device(tensorflow::Device** d) { - TF_RETURN_IF_ERROR(WaitReady()); - DCHECK(IsReady()); - *d = device_; - return tensorflow::Status::OK(); -} - -tensorflow::Status TFE_TensorHandle::OpDevice(tensorflow::Device** d) { - TF_RETURN_IF_ERROR(WaitReady()); - DCHECK(IsReady()); - *d = op_device_; - return tensorflow::Status::OK(); -} - -tensorflow::Status TFE_TensorHandle::TensorAndDevice( - const tensorflow::Tensor** tensor, tensorflow::Device** device, - tensorflow::Device** op_device) { - TF_RETURN_IF_ERROR(WaitReady()); - DCHECK(IsReady()); - *tensor = &tensor_; - *device = device_; - *op_device = op_device_; - return tensorflow::Status::OK(); -} - -void TFE_TensorHandle::SetTensorAndDevice(const tensorflow::Tensor& tensor, - tensorflow::Device* device, - tensorflow::Device* op_device) { - tensorflow::mutex_lock l(ctx_mutex_); - DCHECK(node_id > 0 && ctx_) << "SetTensorAndDevice should be only called " - << "on non-ready handles."; - ctx_ = nullptr; - tensor_ = tensor; - device_ = device; - op_device_ = op_device; -} - TFE_Op::~TFE_Op() { - for (TFE_TensorHandle* h : inputs) { + for (tensorflow::TensorHandle* h : inputs) { h->Unref(); } } diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index 5b29120b40..e6d2ab75ff 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -33,6 +33,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/eager/context.h" #include "tensorflow/core/common_runtime/eager/eager_executor.h" #include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/eager/tensor_handle.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/common_runtime/rendezvous_mgr.h" #include "tensorflow/core/framework/rendezvous.h" @@ -67,84 +68,18 @@ struct TFE_Context { tensorflow::EagerContext context; }; -struct TFE_TensorHandle : public tensorflow::core::RefCounted { - public: +struct TFE_TensorHandle { TFE_TensorHandle(const tensorflow::Tensor& t, tensorflow::Device* d, tensorflow::Device* op_device) - : dtype(t.dtype()), - node_id(0), - tensor_(t), - device_(d), - op_device_(op_device), - ctx_(nullptr) {} + : handle(new tensorflow::TensorHandle(t, d, op_device)) {} TFE_TensorHandle(tensorflow::uint64 node_id, tensorflow::DataType dtype, - TFE_Context* ctx) - : dtype(dtype), - node_id(node_id), - tensor_(dtype), - device_(nullptr), - op_device_(nullptr), - ctx_(ctx) { - DCHECK_GT(node_id, 0); - } - - ~TFE_TensorHandle() override {} - - tensorflow::Status Tensor(const tensorflow::Tensor** t); - - tensorflow::Status Device(tensorflow::Device** d); - - tensorflow::Status OpDevice(tensorflow::Device** d); - - tensorflow::Status TensorAndDevice(const tensorflow::Tensor** tensor, - tensorflow::Device** device, - tensorflow::Device** op_device); - - // Note that this can be called at most once, and only on non-ready handles, - // and makes them ready. - void SetTensorAndDevice(const tensorflow::Tensor& tensor, - tensorflow::Device* device, - tensorflow::Device* op_device); - - // dtype for the handle. It must be the same as t.dtype() once the handle is - // ready. - const tensorflow::DataType dtype; - - private: - // If the contents of the Tensor pointed to by this handle is yet to be - // computed by a EagerNode, this function will block till that compuatation is - // done and the handle is "ready". - tensorflow::Status WaitReady(); - - bool IsReady(); - - // Id for the EagerNode that will compute the value pointed to by this handle. - // If the value is 0, the handle is already ready, but not vice-versa. - const tensorflow::uint64 node_id; - - tensorflow::Tensor tensor_; - - // TODO(ashankar): device_ == nullptr iff local CPU - // This was expedient, but perhaps worth revisiting ('device_' should always - // be a valid pointer?) - // This can be done if TFE_NewOp() and the TFE_TensorHandle constructors are - // provided with the appropriate TFE_Context. - // - // TODO(ashankar): Reference count TFE_Context to ensure that 'device_' of a - // TFE_TensorHandle does not outlive the TFE_Context from which it came? - tensorflow::Device* device_; - - // Device in which the op producing this tensor was executed. Equals to - // device_ for constant tensors. - tensorflow::Device* op_device_; - - tensorflow::mutex ctx_mutex_; - - // `ctx` is only guaranteed to be set if the handle is not "ready". This is - // typically true when the handle was produced during async execution. - // `ctx` object is not owned and should outlive this handle. - TFE_Context* ctx_ GUARDED_BY(ctx_mutex_); + tensorflow::EagerContext* ctx) + : handle(new tensorflow::TensorHandle(node_id, dtype, ctx)) {} + + TFE_TensorHandle(tensorflow::TensorHandle* handle) : handle(handle) {} + + tensorflow::TensorHandle* handle; }; struct TFE_Op { @@ -161,7 +96,7 @@ struct TFE_Op { const tensorflow::string name; tensorflow::AttrBuilder attrs; const tensorflow::AttrTypeMap* attr_types; - tensorflow::gtl::InlinedVector inputs; + tensorflow::gtl::InlinedVector inputs; tensorflow::Device* device; bool use_xla = false; }; diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index de10b10b7e..02fb83200a 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -54,6 +54,29 @@ tf_cuda_library( ], ) +tf_cuda_library( + name = "tensor_handle", + srcs = [ + "tensor_handle.cc", + ], + hdrs = [ + "tensor_handle.h", + ], + visibility = ["//tensorflow:internal"], + deps = [ + ":context", + ":eager_executor", + ":kernel_and_device", + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", + ], +) + tf_cuda_library( name = "kernel_and_device", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.cc b/tensorflow/core/common_runtime/eager/tensor_handle.cc new file mode 100644 index 0000000000..5bc1700627 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/tensor_handle.cc @@ -0,0 +1,107 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/eager/tensor_handle.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/context.h" +#include "tensorflow/core/common_runtime/eager/eager_executor.h" +#include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/framework/rendezvous.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/lib/gtl/stl_util.h" +#include "tensorflow/core/platform/fingerprint.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/platform/thread_annotations.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" + +namespace tensorflow { + +bool TensorHandle::IsReady() { + if (node_id == 0) return true; + mutex_lock l(ctx_mutex_); + return ctx_ == nullptr; +} + +Status TensorHandle::WaitReady() { + if (node_id == 0) return Status::OK(); + EagerExecutor* executor = nullptr; + { + mutex_lock l(ctx_mutex_); + if (ctx_ == nullptr) return Status::OK(); + executor = ctx_->Executor(); + } + return executor->WaitFor(node_id); +} + +Status TensorHandle::Tensor(const tensorflow::Tensor** t) { + TF_RETURN_IF_ERROR(WaitReady()); + DCHECK(IsReady()); + *t = &tensor_; + return Status::OK(); +} + +Status TensorHandle::Device(tensorflow::Device** d) { + TF_RETURN_IF_ERROR(WaitReady()); + DCHECK(IsReady()); + *d = device_; + return Status::OK(); +} + +Status TensorHandle::OpDevice(tensorflow::Device** d) { + TF_RETURN_IF_ERROR(WaitReady()); + DCHECK(IsReady()); + *d = op_device_; + return Status::OK(); +} + +Status TensorHandle::TensorAndDevice(const tensorflow::Tensor** tensor, + tensorflow::Device** device, + tensorflow::Device** op_device) { + TF_RETURN_IF_ERROR(WaitReady()); + DCHECK(IsReady()); + *tensor = &tensor_; + *device = device_; + *op_device = op_device_; + return Status::OK(); +} + +void TensorHandle::SetTensorAndDevice(const tensorflow::Tensor& tensor, + tensorflow::Device* device, + tensorflow::Device* op_device) { + mutex_lock l(ctx_mutex_); + DCHECK(node_id > 0 && ctx_) << "SetTensorAndDevice should be only called " + << "on non-ready handles."; + ctx_ = nullptr; + tensor_ = tensor; + device_ = device; + op_device_ = op_device; +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.h b/tensorflow/core/common_runtime/eager/tensor_handle.h new file mode 100644 index 0000000000..97e67e4652 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/tensor_handle.h @@ -0,0 +1,130 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_TENSOR_HANDLE_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_TENSOR_HANDLE_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/eager/context.h" +#include "tensorflow/core/common_runtime/eager/eager_executor.h" +#include "tensorflow/core/common_runtime/eager/kernel_and_device.h" +#include "tensorflow/core/common_runtime/function.h" +#include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/framework/rendezvous.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/lib/gtl/stl_util.h" +#include "tensorflow/core/platform/fingerprint.h" +#include "tensorflow/core/platform/mutex.h" +#include "tensorflow/core/platform/thread_annotations.h" +#include "tensorflow/core/public/session_options.h" +#include "tensorflow/core/public/version.h" + +namespace tensorflow { + +// Associates a Tensor and a Device, used in the eager runtime. Internal version +// executor_of the TFE_TensorHandle struct and the python EagerTensor class +// (unrelated to python TensorHandle). +class TensorHandle : public core::RefCounted { + public: + TensorHandle(const Tensor& t, Device* d, Device* op_device) + : dtype(t.dtype()), + node_id(0), + tensor_(t), + device_(d), + op_device_(op_device), + ctx_(nullptr) {} + + TensorHandle(uint64 node_id, DataType dtype, EagerContext* ctx) + : dtype(dtype), + node_id(node_id), + tensor_(dtype), + device_(nullptr), + op_device_(nullptr), + ctx_(ctx) { + DCHECK_GT(node_id, 0); + } + + ~TensorHandle() override {} + + Status Tensor(const tensorflow::Tensor** t); + + Status Device(tensorflow::Device** d); + + Status OpDevice(tensorflow::Device** d); + + Status TensorAndDevice(const tensorflow::Tensor** tensor, + tensorflow::Device** device, + tensorflow::Device** op_device); + + // Note that this can be called at most once, and only on non-ready handles, + // and makes them ready. + void SetTensorAndDevice(const tensorflow::Tensor& tensor, + tensorflow::Device* device, + tensorflow::Device* op_device); + + // dtype for the handle. It must be the same as t.dtype() once the handle is + // ready. + const DataType dtype; + + private: + // If the contents of the Tensor pointed to by this handle is yet to be + // computed by a EagerNode, this function will block till that compuatation is + // done and the handle is "ready". + Status WaitReady(); + + bool IsReady(); + + // Id for the EagerNode that will compute the value pointed to by this handle. + // If the value is 0, the handle is already ready, but not vice-versa. + const uint64 node_id; + + tensorflow::Tensor tensor_; + + // TODO(ashankar): device_ == nullptr iff local CPU + // This was expedient, but perhaps worth revisiting ('device_' should always + // be a valid pointer?) + // This can be done if TFE_NewOp() and the TFE_TensorHandle constructors are + // provided with the appropriate TFE_Context. + // + // TODO(ashankar): Reference count TFE_Context to ensure that 'device_' of a + // TFE_TensorHandle does not outlive the TFE_Context from which it came? + tensorflow::Device* device_; + + // Device in which the op producing this tensor was executed. Equals to + // device_ for constant tensors. + tensorflow::Device* op_device_; + + mutex ctx_mutex_; + + // `ctx` is only guaranteed to be set if the handle is not "ready". This is + // typically true when the handle was produced during async execution. + // `ctx` object is not owned and should outlive this handle. + EagerContext* ctx_ GUARDED_BY(ctx_mutex_); +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_TENSOR_HANDLE_H_ diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc index 701f68b8f7..55ba509065 100644 --- a/tensorflow/python/eager/pywrap_tfe_src.cc +++ b/tensorflow/python/eager/pywrap_tfe_src.cc @@ -1013,12 +1013,13 @@ static tensorflow::eager::TapeTensor TapeTensorFromTensor(PyObject* tensor) { TFE_TensorHandle* t = EagerTensor_Handle(tensor); tensorflow::int64 id = EagerTensor_id(tensor); const tensorflow::Tensor* tensor = nullptr; - const tensorflow::Status status = t->Tensor(&tensor); + const tensorflow::Status status = t->handle->Tensor(&tensor); if (MaybeRaiseExceptionFromStatus(status, nullptr)) { - return tensorflow::eager::TapeTensor{id, t->dtype, + return tensorflow::eager::TapeTensor{id, t->handle->dtype, tensorflow::TensorShape({})}; } else { - return tensorflow::eager::TapeTensor{id, t->dtype, tensor->shape()}; + return tensorflow::eager::TapeTensor{id, t->handle->dtype, + tensor->shape()}; } } tensorflow::int64 id = FastTensorId(tensor); diff --git a/tensorflow/python/lib/core/py_func.cc b/tensorflow/python/lib/core/py_func.cc index 02eafd42b3..22317a348c 100644 --- a/tensorflow/python/lib/core/py_func.cc +++ b/tensorflow/python/lib/core/py_func.cc @@ -166,7 +166,7 @@ bool IsSingleNone(PyObject* obj) { // Retrieves a Tensor from `eager_tensor` and stores it in `output_tensor`. tensorflow::Status ExtractTensorFromEagerTensor(const PyObject* eager_tensor, const Tensor** output_tensor) { - return EagerTensor_Handle(eager_tensor)->Tensor(output_tensor); + return EagerTensor_Handle(eager_tensor)->handle->Tensor(output_tensor); } // Calls the registered py function through the trampoline. -- GitLab From 80a878dddcb04512324cb729a4ef5c92510e01a3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 13:47:03 -0700 Subject: [PATCH 168/906] [XLA] Implement the whole graph execution interface and make a test use XlaBuilder. - Add Client::ExecuteGraph. - Make client_library_test_base also (partially) support XlaBuilder by using template. - Make one testcase in the axpy_simple_test use XlaBuilder. The test was slightly changed because currently the builder does not expend implicit broadcast automatically. PiperOrigin-RevId: 190268658 --- tensorflow/compiler/xla/client/BUILD | 1 + tensorflow/compiler/xla/client/client.cc | 51 ++++ tensorflow/compiler/xla/client/client.h | 27 +++ tensorflow/compiler/xla/client/local_client.h | 2 + .../compiler/xla/client/xla_client/BUILD | 13 + .../xla/client/xla_client/xla_builder.cc | 222 +++++++++++++++--- .../xla/client/xla_client/xla_builder.h | 59 +++-- .../xla/client/xla_client/xla_builder_test.cc | 54 ++++- .../xla/client/xla_client/xla_computation.cc | 26 ++ .../xla/client/xla_client/xla_computation.h | 55 +++++ .../xla/service/compile_only_service.cc | 2 +- tensorflow/compiler/xla/service/hlo_module.cc | 7 +- .../compiler/xla/service/local_service.cc | 7 +- tensorflow/compiler/xla/service/service.cc | 67 +++++- tensorflow/compiler/xla/service/service.h | 4 +- tensorflow/compiler/xla/tests/BUILD | 2 + .../compiler/xla/tests/axpy_simple_test.cc | 9 +- .../xla/tests/client_library_test_base.cc | 74 +++++- .../xla/tests/client_library_test_base.h | 52 ++-- 19 files changed, 619 insertions(+), 115 deletions(-) create mode 100644 tensorflow/compiler/xla/client/xla_client/xla_computation.cc create mode 100644 tensorflow/compiler/xla/client/xla_client/xla_computation.h diff --git a/tensorflow/compiler/xla/client/BUILD b/tensorflow/compiler/xla/client/BUILD index 02356699a2..5094e5ce67 100644 --- a/tensorflow/compiler/xla/client/BUILD +++ b/tensorflow/compiler/xla/client/BUILD @@ -74,6 +74,7 @@ cc_library( "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla:xla_proto", + "//tensorflow/compiler/xla/client/xla_client:xla_computation", "//tensorflow/compiler/xla/legacy_flags:debug_options_flags", "//tensorflow/compiler/xla/service:session_proto", "//tensorflow/core:lib", diff --git a/tensorflow/compiler/xla/client/client.cc b/tensorflow/compiler/xla/client/client.cc index d15ccb0c28..5ce3c45528 100644 --- a/tensorflow/compiler/xla/client/client.cc +++ b/tensorflow/compiler/xla/client/client.cc @@ -177,6 +177,22 @@ StatusOr> Client::ExecuteAndTransfer( return Transfer(*data, shape_with_output_layout); } +StatusOr> Client::ExecuteAndTransfer( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const ExecutionOptions* execution_options, + ExecutionProfile* execution_profile) { + TF_ASSIGN_OR_RETURN( + std::unique_ptr data, + Execute(computation, arguments, execution_options, execution_profile)); + + const Shape* shape_with_output_layout = nullptr; + if (execution_options && execution_options->has_shape_with_output_layout()) { + shape_with_output_layout = &execution_options->shape_with_output_layout(); + } + return Transfer(*data, shape_with_output_layout); +} + StatusOr Client::LoadSnapshot(const SessionModule& module) { LoadComputationSnapshotRequest request; *request.mutable_module() = module; @@ -231,6 +247,41 @@ StatusOr> Client::Execute( return MakeUnique(stub_, response.output()); } +StatusOr> Client::Execute( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const ExecutionOptions* execution_options, + ExecutionProfile* execution_profile) { + ExecuteGraphRequest request; + *request.mutable_computation() = computation.proto(); + + if (execution_options == nullptr) { + *request.mutable_execution_options() = CreateDefaultExecutionOptions(); + } else { + *request.mutable_execution_options() = *execution_options; + } + for (GlobalData* argument : arguments) { + CHECK(argument != nullptr) << "Argument pointers must not be null."; + *request.add_arguments() = argument->handle(); + } + + ExecuteResponse response; + VLOG(1) << "making execute request: " << request.ShortDebugString(); + Status s = stub_->ExecuteGraph(&request, &response); + VLOG(1) << "done with request"; + + if (!s.ok()) { + return s; + } + + if (execution_profile != nullptr) { + *execution_profile = response.profile(); + // TODO(b/74197823): Get execution stats for the graph and VLOG(1) them. + } + + return MakeUnique(stub_, response.output()); +} + StatusOr>> Client::ExecuteParallel( tensorflow::gtl::ArraySlice computations) { ExecuteParallelRequest request; diff --git a/tensorflow/compiler/xla/client/client.h b/tensorflow/compiler/xla/client/client.h index c28380b689..ec87646ebf 100644 --- a/tensorflow/compiler/xla/client/client.h +++ b/tensorflow/compiler/xla/client/client.h @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation.h" #include "tensorflow/compiler/xla/client/global_data.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/service/session.pb.h" #include "tensorflow/compiler/xla/service_interface.h" @@ -57,6 +58,21 @@ class Client { const ExecutionOptions* execution_options = nullptr, ExecutionProfile* execution_profile = nullptr); + // Executes the computation with the given arguments and returns the global + // data that was produced from the execution. + // * If execution_options is not nullptr, these options are passed to the + // service to affect how it compiles our computation. (The pointer does not + // need to live beyond this call.) + // * If execution_profile is not nullptr then the pointed-to ExecutionProfile + // will be filled with profile data from the execution. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> Execute( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const ExecutionOptions* execution_options = nullptr, + ExecutionProfile* execution_profile = nullptr); + // A struct to represent a computation instance to be executed. // * If execution_options.device_handles is not empty, the computation is // executed on the devices associated with the handles by partitioning the @@ -137,6 +153,17 @@ class Client { const ExecutionOptions* execution_options = nullptr, ExecutionProfile* execution_profile = nullptr); + // Executes the computation with the given arguments and transfers the result + // to the client as a literal. Parameters are defined the same as for + // Execute() and Transfer(). + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> ExecuteAndTransfer( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const ExecutionOptions* execution_options = nullptr, + ExecutionProfile* execution_profile = nullptr); + // Unregister the memory for the given GlobalData on the device. Status Unregister(const GlobalData& data); diff --git a/tensorflow/compiler/xla/client/local_client.h b/tensorflow/compiler/xla/client/local_client.h index de0ed13c43..2e5d85ba68 100644 --- a/tensorflow/compiler/xla/client/local_client.h +++ b/tensorflow/compiler/xla/client/local_client.h @@ -123,6 +123,8 @@ class LocalClient : public Client { const tensorflow::gtl::ArraySlice argument_layouts, const ExecutableBuildOptions& options); + // TODO(b/74197823): Add a overload of Compile for XlaComputation. + // Copy the literal data to the device with the given ordinal and return as a // ScopedShapedBuffer. If non-null the given memory allocator is used for // device memory allocation. If null, the default memory allocator for the diff --git a/tensorflow/compiler/xla/client/xla_client/BUILD b/tensorflow/compiler/xla/client/xla_client/BUILD index 69df15c988..cc5f551c9c 100644 --- a/tensorflow/compiler/xla/client/xla_client/BUILD +++ b/tensorflow/compiler/xla/client/xla_client/BUILD @@ -25,12 +25,25 @@ filegroup( load("//tensorflow:tensorflow.bzl", "tf_cc_test") +cc_library( + name = "xla_computation", + srcs = ["xla_computation.cc"], + hdrs = ["xla_computation.h"], + deps = [ + "//tensorflow/compiler/xla:status_macros", + "//tensorflow/compiler/xla:xla_data_proto", + "//tensorflow/compiler/xla/service:hlo_proto", + "//tensorflow/core:lib", + ], +) + # TODO(b/74197823): Replace computation_builder with xla_builder. cc_library( name = "xla_builder", srcs = ["xla_builder.cc"], hdrs = ["xla_builder.h"], deps = [ + ":xla_computation", "//tensorflow/compiler/xla:literal_util", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:status_macros", diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index ec646cabe9..90f2b2d73a 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" +#include #include #include @@ -80,40 +81,32 @@ void XlaBuilder::NoteError(const Status& error) { } } -StatusOr XlaBuilder::Build() { - if (!first_error_.ok()) { - string backtrace; - first_error_backtrace_.Dump(tensorflow::DebugWriteToString, &backtrace); - return AppendStatus(first_error_, backtrace); - } - - HloComputationProto entry; - ProgramShape* program_shape = entry.mutable_program_shape(); - - entry.set_name(name_); +StatusOr XlaBuilder::GetProgramShape(int64* root_id) { + TF_RET_CHECK(root_id != nullptr); + ProgramShape program_shape; // Not all instructions can be roots. Walk backwards from the last added // instruction until a valid root is found. - entry.set_root_id(-1); - for (int64 i = instructions_.size() - 1; i >= 0; i--) { + int64 index = instructions_.size() - 1; + for (; index >= 0; index--) { TF_ASSIGN_OR_RETURN(HloOpcode opcode, - StringToHloOpcode(instructions_[i].opcode())); + StringToHloOpcode(instructions_[index].opcode())); if (CanBeRoot(opcode)) { - entry.set_root_id(instructions_[i].id()); - *program_shape->mutable_result() = instructions_[i].shape(); break; } } - if (entry.root_id() == -1) { + if (index < 0) { return FailedPrecondition("no root instruction was found"); } + *root_id = instructions_[index].id(); + *program_shape.mutable_result() = instructions_[index].shape(); // Check that the parameter numbers are continuous from 0, and add parameter // shapes and names to the program shape. const int64 param_count = parameter_numbers_.size(); for (int64 i = 0; i < param_count; i++) { - program_shape->add_parameters(); - program_shape->add_parameter_names(); + program_shape.add_parameters(); + program_shape.add_parameter_names(); } for (const HloInstructionProto& instr : instructions_) { // Parameter number uniqueness is guaranteed in XlaBuilder::Parameter(). So @@ -123,10 +116,35 @@ StatusOr XlaBuilder::Build() { const int64 index = instr.parameter_number(); TF_RET_CHECK(index >= 0 && index < param_count) << "invalid parameter number: " << index; - *program_shape->mutable_parameters(index) = instr.shape(); - *program_shape->mutable_parameter_names(index) = instr.name(); + *program_shape.mutable_parameters(index) = instr.shape(); + *program_shape.mutable_parameter_names(index) = instr.name(); } } + return program_shape; +} + +StatusOr XlaBuilder::GetProgramShape() { + int64 root_id; + return GetProgramShape(&root_id); +} + +StatusOr XlaBuilder::Build() { + if (!first_error_.ok()) { + string backtrace; + first_error_backtrace_.Dump(tensorflow::DebugWriteToString, &backtrace); + return AppendStatus(first_error_, backtrace); + } + + HloComputationProto entry; + entry.set_name(name_); + + { + int64 root_id; + ProgramShape program_shape; + TF_ASSIGN_OR_RETURN(program_shape, GetProgramShape(&root_id)); + entry.mutable_program_shape()->Swap(&program_shape); + entry.set_root_id(root_id); + } for (auto& instruction : instructions_) { entry.add_instructions()->Swap(&instruction); @@ -149,19 +167,120 @@ StatusOr XlaBuilder::Build() { return std::move(computation); } -XlaOp XlaBuilder::Add(const XlaOp& lhs, const XlaOp& rhs, - tensorflow::gtl::ArraySlice broadcast_dimensions) { - auto op = [&]() -> StatusOr { +StatusOr XlaBuilder::InDimBroadcast( + const Shape& shape, const XlaOp& operand, + tensorflow::gtl::ArraySlice broadcast_dimensions) { + HloInstructionProto instr; + *instr.mutable_shape() = shape; + for (int64 dim : broadcast_dimensions) { + instr.add_dimensions(dim); + } + return AddInstruction(std::move(instr), HloOpcode::kBroadcast, {operand}); +} + +StatusOr XlaBuilder::AddBroadcastSequence(const Shape& output_shape, + const XlaOp& operand) { + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, operand.GetShape()); + + CHECK(ShapeUtil::IsScalar(operand_shape) || + ShapeUtil::Rank(operand_shape) == ShapeUtil::Rank(output_shape)); + Shape broadcast_shape = + ShapeUtil::ChangeElementType(output_shape, operand_shape.element_type()); + + // Do explicit broadcast for scalar. + if (ShapeUtil::IsScalar(operand_shape)) { + return InDimBroadcast(broadcast_shape, operand, {}); + } + + // Do explicit broadcast for degenerate broadcast. + std::vector broadcast_dimensions; + std::vector reshaped_dimensions; + for (int i = 0; i < ShapeUtil::Rank(operand_shape); i++) { + if (operand_shape.dimensions(i) == output_shape.dimensions(i)) { + broadcast_dimensions.push_back(i); + reshaped_dimensions.push_back(operand_shape.dimensions(i)); + } else { + TF_RET_CHECK(operand_shape.dimensions(i) == 1) + << "An explicit broadcast sequence requires the broadcasted " + "dimensions to be trivial; operand shape: " + << operand_shape << "; output_shape: " << output_shape; + } + } + // Eliminate the size one dimensions. + TF_ASSIGN_OR_RETURN(XlaOp reshaped_operand, + Reshape(ShapeUtil::MakeShape(operand_shape.element_type(), + reshaped_dimensions), + operand)); + // Broadcast 'reshape' up to the larger size. + return InDimBroadcast(broadcast_shape, reshaped_operand, + broadcast_dimensions); +} + +XlaOp XlaBuilder::BinaryOp( + HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs, + tensorflow::gtl::ArraySlice broadcast_dimensions) { + return NoteErrorOrReturn([&]() -> StatusOr { HloInstructionProto instr; TF_ASSIGN_OR_RETURN(const Shape& lhs_shape, lhs.GetShape()); TF_ASSIGN_OR_RETURN(const Shape& rhs_shape, rhs.GetShape()); - TF_ASSIGN_OR_RETURN( - *instr.mutable_shape(), - ShapeInference::InferBinaryOpShape(HloOpcode::kAdd, lhs_shape, - rhs_shape, broadcast_dimensions)); - return AddInstruction(std::move(instr), HloOpcode::kAdd, {lhs, rhs}); - }; - return NoteErrorOrReturn(op()); + TF_ASSIGN_OR_RETURN(*instr.mutable_shape(), + ShapeInference::InferBinaryOpShape( + binop, lhs_shape, rhs_shape, broadcast_dimensions)); + + const int64 lhs_rank = ShapeUtil::Rank(lhs_shape); + const int64 rhs_rank = ShapeUtil::Rank(rhs_shape); + + XlaOp updated_lhs = lhs; + XlaOp updated_rhs = rhs; + + if (!broadcast_dimensions.empty() && lhs_rank != rhs_rank) { + const bool should_broadcast_lhs = lhs_rank < rhs_rank; + XlaOp from = should_broadcast_lhs ? lhs : rhs; + const Shape& from_shape = should_broadcast_lhs ? lhs_shape : rhs_shape; + + std::vector to_size; + for (int64 size : instr.shape().dimensions()) { + to_size.push_back(size); + } + for (int64 from_dim = 0; from_dim < ShapeUtil::Rank(from_shape); + from_dim++) { + int64 to_dim = broadcast_dimensions[from_dim]; + to_size[to_dim] = from_shape.dimensions(from_dim); + } + + const Shape& broadcasted_shape = + ShapeUtil::MakeShape(from_shape.element_type(), to_size); + TF_ASSIGN_OR_RETURN( + XlaOp broadcasted_operand, + InDimBroadcast(broadcasted_shape, from, broadcast_dimensions)); + + updated_lhs = should_broadcast_lhs ? broadcasted_operand : lhs; + updated_rhs = !should_broadcast_lhs ? broadcasted_operand : rhs; + } + + TF_ASSIGN_OR_RETURN(Shape updated_lhs_shape, updated_lhs.GetShape()); + if (!ShapeUtil::SameDimensions(instr.shape(), updated_lhs_shape)) { + TF_ASSIGN_OR_RETURN(updated_lhs, + AddBroadcastSequence(instr.shape(), updated_lhs)); + } + TF_ASSIGN_OR_RETURN(Shape updated_rhs_shape, updated_rhs.GetShape()); + if (!ShapeUtil::SameDimensions(instr.shape(), updated_rhs_shape)) { + TF_ASSIGN_OR_RETURN(updated_rhs, + AddBroadcastSequence(instr.shape(), updated_rhs)); + } + + return AddInstruction(std::move(instr), binop, {updated_lhs, updated_rhs}); + }()); +} + +XlaOp XlaBuilder::Add(const XlaOp& lhs, const XlaOp& rhs, + tensorflow::gtl::ArraySlice broadcast_dimensions) { + return BinaryOp(HloOpcode::kAdd, lhs, rhs, broadcast_dimensions); +} + +XlaOp XlaBuilder::Mul(const XlaOp& lhs, const XlaOp& rhs, + tensorflow::gtl::ArraySlice broadcast_dimensions) { + return BinaryOp(HloOpcode::kMultiply, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::ConstantLiteral(const Literal& literal) { @@ -173,7 +292,7 @@ XlaOp XlaBuilder::ConstantLiteral(const Literal& literal) { XlaOp XlaBuilder::Call(const XlaComputation& computation, tensorflow::gtl::ArraySlice operands) { - auto op = [&]() -> StatusOr { + return NoteErrorOrReturn([&]() -> StatusOr { HloInstructionProto instr; std::vector operand_shape_ptrs; std::vector operand_shapes; @@ -196,13 +315,12 @@ XlaOp XlaBuilder::Call(const XlaComputation& computation, } return AddInstruction(std::move(instr), HloOpcode::kCall, operands); - }; - return NoteErrorOrReturn(op()); + }()); } XlaOp XlaBuilder::Parameter(int64 parameter_number, const Shape& shape, const string& name) { - auto op = [&]() -> StatusOr { + return NoteErrorOrReturn([&]() -> StatusOr { HloInstructionProto instr; if (parameter_numbers_.find(parameter_number) != parameter_numbers_.end()) { return InvalidArgument("parameter %lld already registered", @@ -213,8 +331,37 @@ XlaOp XlaBuilder::Parameter(int64 parameter_number, const Shape& shape, instr.set_name(name); *instr.mutable_shape() = shape; return AddInstruction(std::move(instr), HloOpcode::kParameter); - }; - return NoteErrorOrReturn(op()); + }()); +} + +XlaOp XlaBuilder::Broadcast( + const XlaOp& operand, tensorflow::gtl::ArraySlice broadcast_sizes) { + return NoteErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, operand.GetShape()); + TF_ASSIGN_OR_RETURN( + const Shape& shape, + ShapeInference::InferBroadcastShape(operand_shape, broadcast_sizes)); + + // The client-level broadcast op just appends dimensions on the left (adds + // lowest numbered dimensions). The HLO broadcast instruction is more + // flexible and can add new dimensions anywhere. The instruction's + // dimensions field maps operand dimensions to dimensions in the broadcast + // output, so to append dimensions on the left the instruction's dimensions + // should just be the n highest dimension numbers of the output shape where + // n is the number of input dimensions. + const int64 operand_rank = ShapeUtil::Rank(operand_shape); + std::vector dimensions(operand_rank); + for (int i = 0; i < operand_rank; ++i) { + dimensions[i] = i + ShapeUtil::Rank(shape) - operand_rank; + } + return InDimBroadcast(shape, operand, dimensions); + }()); +} + +StatusOr XlaBuilder::Reshape(const Shape& shape, const XlaOp& operand) { + HloInstructionProto instr; + *instr.mutable_shape() = shape; + return AddInstruction(std::move(instr), HloOpcode::kReshape, {operand}); } XlaOp XlaBuilder::Slice(const XlaOp& operand, @@ -660,6 +807,7 @@ XlaOp XlaBuilder::AddInstruction(HloInstructionProto&& instr, HloOpcode opcode, } for (const auto& operand : operands) { instr.add_operand_ids(operand.handle()); + // TODO(b/74197823): Set metadata and sharding. } instructions_.push_back(instr); diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index 455ea3d9cc..407b2df274 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -25,6 +25,7 @@ limitations under the License. #include #include "tensorflow/compiler/xla/client/padding.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/service/hlo.pb.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" @@ -65,38 +66,6 @@ class XlaOp { XlaBuilder* builder_; // Not owned. }; -// The computation graph that the user builds up with the XlaBuilder. -// -// TODO(b/74197823): Replace xla::Computation with this one. -class XlaComputation { - public: - XlaComputation(const XlaComputation&) = delete; - XlaComputation& operator=(const XlaComputation&) = delete; - - XlaComputation(XlaComputation&& from) { *this = std::move(from); } - - XlaComputation& operator=(XlaComputation&& from) { - proto_ = std::move(from.proto()); - unique_id_ = from.unique_id_; - return *this; - } - - // Returns the "program shape" (parameter and return shapes) for this - // computation. - const ProgramShape& GetProgramShape() const { return proto_.program_shape(); } - - const HloModuleProto& proto() const { return proto_; } - - private: - // Creates a null Computation. - XlaComputation(const int64 unique_id) : unique_id_(unique_id) {} - HloModuleProto* mutable_proto() { return &proto_; } - friend class XlaBuilder; - - int64 unique_id_; - HloModuleProto proto_; -}; - // A convenient interface for building up computations. // // Thread-compatible. @@ -733,6 +702,9 @@ class XlaBuilder { // Returns the shape of the given op. StatusOr GetShape(const XlaOp& op) const; + // Returns the (inferred) result for the current computation's shape. + StatusOr GetProgramShape(); + private: XlaOp AddInstruction(HloInstructionProto&& instr, HloOpcode opcode, tensorflow::gtl::ArraySlice operands = {}); @@ -756,6 +728,29 @@ class XlaBuilder { StatusOr LookUpInstruction(const XlaOp& op) const; + // Internal helper method that does the building for an arbitrary binary op. + // broadcast_dimensions specifies which dimensions to use for broadcasting + // when the operation is between tensors of different ranks. + XlaOp BinaryOp(HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs, + tensorflow::gtl::ArraySlice broadcast_dimensions); + + StatusOr InDimBroadcast( + const Shape& shape, const XlaOp& operand, + tensorflow::gtl::ArraySlice broadcast_dimensions); + + // Internal helper method that creates a sequence of instructions that + // performs an explicit broadcast of the operand to the target shape. + StatusOr AddBroadcastSequence(const Shape& output_shape, + const XlaOp& operand); + + // Internal helper method for creating a Reshape op with the already inferred + // shape. + StatusOr Reshape(const Shape& shape, const XlaOp& operand); + + // Returns the (inferred) result for the program shape for the current + // computation and fills the root_id in the pointer. + StatusOr GetProgramShape(int64* root_id); + string name_; // Name to use for the built computation. // The first error encountered while building the computation. diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc index a400e4e78b..10d8fa1622 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc @@ -57,16 +57,16 @@ TEST_F(XlaBuilderTest, OnePlusTwo) { EXPECT_THAT(root, op::Add(op::Constant(), op::Constant())); } -TEST_F(XlaBuilderTest, ParamPlusConstant) { +TEST_F(XlaBuilderTest, ParamPlusConstantHasScalarBroadcast) { XlaBuilder b(TestName()); auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {3, 5}), "x"); b.Add(x, b.ConstantR0(1.0)); TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); auto root = module->entry_computation()->root_instruction(); - EXPECT_THAT(root, op::Add(op::Parameter(), op::Constant())); + EXPECT_THAT(root, op::Add(op::Parameter(), op::Broadcast(op::Constant()))); } -TEST_F(XlaBuilderTest, ParamPlusParam) { +TEST_F(XlaBuilderTest, ParamPlusParamHasBroadcast) { XlaBuilder b(TestName()); const auto& x_shape = ShapeUtil::MakeShape(S32, {2, 4, 6}); const auto& y_shape = ShapeUtil::MakeShape(S32, {2, 4}); @@ -79,7 +79,7 @@ TEST_F(XlaBuilderTest, ParamPlusParam) { TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); auto root = module->entry_computation()->root_instruction(); - EXPECT_THAT(root, op::Add(op::Parameter(0), op::Parameter(1))); + EXPECT_THAT(root, op::Add(op::Parameter(0), op::Broadcast(op::Parameter(1)))); } TEST_F(XlaBuilderTest, XPlusX) { @@ -133,5 +133,51 @@ TEST_F(XlaBuilderTest, Call) { op::Call(op::Constant(), op::Constant()))); } +TEST_F(XlaBuilderTest, BinopHasDegenerateBroadcast) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {1, 2, 3}), "x"); + auto y = b.Parameter(1, ShapeUtil::MakeShape(F32, {1, 2, 1}), "y"); + b.Add(x, y); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + + // Expected: + // + // x: f32[1,2,3] y: f32[1,2,1] + // | | + // | reshape: f32[1,2] + // | | + // | broadcast: f32[1,2,3] + // \ / + // add + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, op::Add(op::Parameter(0), + op::Broadcast(op::Reshape(op::Parameter(1))))); +} + +TEST_F(XlaBuilderTest, BinopHasInDimAndDegenerateBroadcast) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {2, 3}), "x"); + auto y = b.Parameter(1, ShapeUtil::MakeShape(F32, {2, 1, 4}), "y"); + b.Add(x, y, /*broadcast_dimensions=*/{0, 1}); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + + // The binary operation has in-dim broadcast and degenerate broadcast, should + // first do the in-dim broadcast then convert the degnerate broadcast into a + // reshape and a broadcast. + // + // Expected: + // + // x: f32[2,3] y: f32[2,1,4] + // | | + // broadcast: f32[2,3,4] reshape: f32[2,4] + // | | + // | broadcast: f32[2,3,4] + // \ / + // add + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, op::Add(op::Broadcast(op::Parameter(0)), + op::Broadcast(op::Reshape(op::Parameter(1))))); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.cc b/tensorflow/compiler/xla/client/xla_client/xla_computation.cc new file mode 100644 index 0000000000..3681792eee --- /dev/null +++ b/tensorflow/compiler/xla/client/xla_client/xla_computation.cc @@ -0,0 +1,26 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h" + +#include + +namespace xla { + +const ProgramShape& XlaComputation::GetProgramShape() const { + return proto_.program_shape(); +} + +} // namespace xla diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.h b/tensorflow/compiler/xla/client/xla_client/xla_computation.h new file mode 100644 index 0000000000..5b89747fdd --- /dev/null +++ b/tensorflow/compiler/xla/client/xla_client/xla_computation.h @@ -0,0 +1,55 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_ +#define TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_ + +#include + +#include "tensorflow/compiler/xla/service/hlo.pb.h" +#include "tensorflow/compiler/xla/status_macros.h" +#include "tensorflow/compiler/xla/xla_data.pb.h" + +namespace xla { + +// The computation graph that the user builds up with the XlaBuilder. +// +// TODO(b/74197823): Replace xla::Computation with this one. +class XlaComputation { + public: + XlaComputation(const XlaComputation&) = delete; + XlaComputation& operator=(const XlaComputation&) = delete; + + XlaComputation(XlaComputation&& from) = default; + + XlaComputation& operator=(XlaComputation&& from) = default; + + // Returns the "program shape" (parameter and return shapes) for this + // computation. + const ProgramShape& GetProgramShape() const; + const HloModuleProto& proto() const { return proto_; } + + private: + XlaComputation(const int64 unique_id) : unique_id_(unique_id) {} + HloModuleProto* mutable_proto() { return &proto_; } + friend class XlaBuilder; + + int64 unique_id_; + HloModuleProto proto_; +}; + +} // namespace xla + +#endif // TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_ diff --git a/tensorflow/compiler/xla/service/compile_only_service.cc b/tensorflow/compiler/xla/service/compile_only_service.cc index 6664496ab6..c83da9eddc 100644 --- a/tensorflow/compiler/xla/service/compile_only_service.cc +++ b/tensorflow/compiler/xla/service/compile_only_service.cc @@ -100,7 +100,7 @@ CompileOnlyService::CompileAheadOfTime( TF_ASSIGN_OR_RETURN( std::unique_ptr module_config, CreateModuleConfig(*program_shape, instance.argument_layouts, - &execution_options, *user_computation)); + &execution_options, user_computation)); TF_ASSIGN_OR_RETURN(std::unique_ptr hlo_module, computation_tracker_.BuildHloModule( diff --git a/tensorflow/compiler/xla/service/hlo_module.cc b/tensorflow/compiler/xla/service/hlo_module.cc index 2037764dae..595c531ccf 100644 --- a/tensorflow/compiler/xla/service/hlo_module.cc +++ b/tensorflow/compiler/xla/service/hlo_module.cc @@ -237,8 +237,8 @@ StatusOr> HloModule::CreateFromProto( for (int i = 0; i < expected_program_shape.parameters_size(); ++i) { const Shape& parameter_shape = module_config.entry_computation_layout().parameter_layout(i).shape(); - TF_RET_CHECK( - ShapeUtil::Equal(expected_program_shape.parameters(i), parameter_shape)) + TF_RET_CHECK(ShapeUtil::Compatible(expected_program_shape.parameters(i), + parameter_shape)) << "HloModuleConfig has different shape for parameter " << i << " than the HLO module. Expected: " << ShapeUtil::HumanStringWithLayout( @@ -247,7 +247,8 @@ StatusOr> HloModule::CreateFromProto( } const Shape& result_shape = module_config.entry_computation_layout().result_layout().shape(); - TF_RET_CHECK(ShapeUtil::Equal(expected_program_shape.result(), result_shape)) + TF_RET_CHECK( + ShapeUtil::Compatible(expected_program_shape.result(), result_shape)) << "HloModuleConfig has different result shape than the HLO module. " "Expected: " << ShapeUtil::HumanStringWithLayout(expected_program_shape.result()) diff --git a/tensorflow/compiler/xla/service/local_service.cc b/tensorflow/compiler/xla/service/local_service.cc index 5690a89909..1e2d8eea58 100644 --- a/tensorflow/compiler/xla/service/local_service.cc +++ b/tensorflow/compiler/xla/service/local_service.cc @@ -146,10 +146,9 @@ StatusOr> LocalService::CompileExecutable( LayoutUtil::SetToDefaultLayout( execution_options.mutable_shape_with_output_layout()); } - TF_ASSIGN_OR_RETURN( - std::unique_ptr module_config, - CreateModuleConfig(*program_shape, argument_layouts, &execution_options, - *user_computation)); + TF_ASSIGN_OR_RETURN(std::unique_ptr module_config, + CreateModuleConfig(*program_shape, argument_layouts, + &execution_options, user_computation)); TF_ASSIGN_OR_RETURN( se::StreamExecutor * executor, diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 0becc9d8f8..04487a4795 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -272,7 +272,7 @@ StatusOr> Service::CreateModuleConfig( const ProgramShape& program_shape, tensorflow::gtl::ArraySlice argument_shapes, const ExecutionOptions* execution_options, - const UserComputation& user_computation) { + const UserComputation* user_computation) { auto config = MakeUnique(program_shape); auto* computation_layout = config->mutable_entry_computation_layout(); @@ -286,8 +286,15 @@ StatusOr> Service::CreateModuleConfig( // ProgramShape. if (!ShapeUtil::Compatible(*argument_shapes[i], program_shape.parameters(i))) { + if (user_computation == nullptr) { + return InvalidArgument( + "Argument does not match shape of computation parameter %d: want " + "%s, got %s", + i, ShapeUtil::HumanString(program_shape.parameters(i)).c_str(), + ShapeUtil::HumanString(*argument_shapes[i]).c_str()); + } return InvalidParameterArgument( - *user_computation.ParameterMetadata(i).value(), + *user_computation->ParameterMetadata(i).value(), "Argument does not match shape of computation parameter %d: want %s, " "got %s", i, ShapeUtil::HumanString(program_shape.parameters(i)).c_str(), @@ -330,7 +337,7 @@ StatusOr> Service::CreateModuleConfig( const ProgramShape& program_shape, tensorflow::gtl::ArraySlice arguments, const ExecutionOptions& execution_options, - const UserComputation& user_computation) { + const UserComputation* user_computation) { std::vector argument_shapes; for (const auto* arg : arguments) { argument_shapes.push_back(&arg->on_host_shape()); @@ -778,7 +785,7 @@ tensorflow::Status Service::ExecuteParallel(const ExecuteParallelRequest* arg, TF_ASSIGN_OR_RETURN( std::unique_ptr module_config, CreateModuleConfig(*program_shape, replicated_arguments.front(), - request.execution_options(), *user_computation)); + request.execution_options(), user_computation)); VLOG(3) << "ExecuteParallel created HloModuleConfig computation layout: " << module_config->entry_computation_layout().ToString(); @@ -894,7 +901,7 @@ tensorflow::Status Service::Execute(const ExecuteRequest* arg, TF_ASSIGN_OR_RETURN( std::unique_ptr module_config, CreateModuleConfig(*program_shape, replicated_arguments.front(), - arg->execution_options(), *user_computation)); + arg->execution_options(), user_computation)); VLOG(3) << "Execute created HloModuleConfig computation layout: " << module_config->entry_computation_layout().ToString(); @@ -935,9 +942,49 @@ tensorflow::Status Service::Execute(const ExecuteRequest* arg, return tensorflow::Status::OK(); } -tensorflow::Status Service::ExecuteGraph(const ExecuteGraphRequest* /*arg*/, - ExecuteResponse* /*result*/) { - return Unimplemented("execute-graph is not yet implemented"); +tensorflow::Status Service::ExecuteGraph(const ExecuteGraphRequest* arg, + ExecuteResponse* result) { + VLOG(1) << "running execute-graph request"; + + if (!arg->has_computation()) { + return InvalidArgument("computations may not be empty"); + } + + // TODO(b/74197823): Handle partitioning. + + TF_ASSIGN_OR_RETURN(auto replicas, Replicas(*execute_backend_, + SingleComputationDeviceHandle())); + TF_ASSIGN_OR_RETURN( + std::vector> replicated_arguments, + ResolveAndValidateArguments(arg->arguments(), replicas)); + + TF_ASSIGN_OR_RETURN(const auto& config, + CreateModuleConfig(arg->computation().program_shape(), + replicated_arguments.front(), + arg->execution_options())); + + TF_ASSIGN_OR_RETURN(std::unique_ptr module, + HloModule::CreateFromProto(arg->computation(), *config)); + TF_RETURN_IF_ERROR(MaybeDumpHloModule(*module)); + + TF_ASSIGN_OR_RETURN(module, execute_backend_->compiler()->RunHloPasses( + std::move(module), + execute_backend_->default_stream_executor(), + /*device_allocator=*/nullptr)); + TF_ASSIGN_OR_RETURN( + std::unique_ptr executable, + execute_backend_->compiler()->RunBackend( + std::move(module), execute_backend_->default_stream_executor(), + /*device_allocator=*/nullptr)); + + TF_ASSIGN_OR_RETURN( + *result->mutable_output(), + ExecuteAndRegisterResult( + executable.get(), replicated_arguments, execute_backend_.get(), + "result of " + arg->computation().name(), result->mutable_profile())); + + VLOG(1) << "successfully completed 'execute-graph' request"; + return tensorflow::Status::OK(); } tensorflow::Status Service::ExecuteAsync(const ExecuteAsyncRequest* arg, @@ -967,7 +1014,7 @@ tensorflow::Status Service::ExecuteAsync(const ExecuteAsyncRequest* arg, TF_ASSIGN_OR_RETURN( std::unique_ptr module_config, CreateModuleConfig(*program_shape, replicated_arguments.front(), - arg->execution_options(), *user_computation)); + arg->execution_options(), user_computation)); VLOG(3) << "ExecuteAsync created HloModuleConfig computation layout: " << module_config->entry_computation_layout().ToString(); @@ -1268,7 +1315,7 @@ tensorflow::Status Service::ComputeConstant(const ComputeConstantRequest* arg, TF_ASSIGN_OR_RETURN(std::unique_ptr module_config, CreateModuleConfig(program_shape, {}, execution_options, - *user_computation)); + user_computation)); // Exclude dead parameter instructions for the purpose of computing constants. TF_ASSIGN_OR_RETURN( diff --git a/tensorflow/compiler/xla/service/service.h b/tensorflow/compiler/xla/service/service.h index 96352d9096..a76bdd89c7 100644 --- a/tensorflow/compiler/xla/service/service.h +++ b/tensorflow/compiler/xla/service/service.h @@ -258,7 +258,7 @@ class Service : public ServiceInterface { const ProgramShape& program_shape, tensorflow::gtl::ArraySlice arguments, const ExecutionOptions& execution_options, - const UserComputation& user_computation); + const UserComputation* user_computation = nullptr); protected: friend class LocalExecutable; @@ -286,7 +286,7 @@ class Service : public ServiceInterface { const ProgramShape& program_shape, tensorflow::gtl::ArraySlice argument_shapes, const ExecutionOptions* execution_options, - const UserComputation& user_computation); + const UserComputation* user_computation = nullptr); // Builds an Executable for the given parameters. // diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 7fb7919674..e81e862c49 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -190,6 +190,7 @@ cc_library( "//tensorflow/compiler/xla/client:computation_builder", "//tensorflow/compiler/xla/client:global_data", "//tensorflow/compiler/xla/client:local_client", + "//tensorflow/compiler/xla/client/xla_client:xla_builder", "//tensorflow/compiler/xla/tests:literal_test_util", "//tensorflow/compiler/xla/tests:test_utils", "//tensorflow/core:lib", @@ -386,6 +387,7 @@ xla_test( deps = [ "//tensorflow/compiler/xla/client:computation_builder", "//tensorflow/compiler/xla/client:local_client", + "//tensorflow/compiler/xla/client/xla_client:xla_builder", "//tensorflow/compiler/xla/tests:client_library_test_base", "//tensorflow/compiler/xla/tests:literal_test_util", "//tensorflow/compiler/xla/tests:xla_internal_test_main", diff --git a/tensorflow/compiler/xla/tests/axpy_simple_test.cc b/tensorflow/compiler/xla/tests/axpy_simple_test.cc index 3f6fd7c65d..ec3b46acfe 100644 --- a/tensorflow/compiler/xla/tests/axpy_simple_test.cc +++ b/tensorflow/compiler/xla/tests/axpy_simple_test.cc @@ -17,6 +17,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/client/local_client.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" #include "tensorflow/compiler/xla/tests/client_library_test_base.h" #include "tensorflow/compiler/xla/tests/literal_test_util.h" #include "tensorflow/compiler/xla/tests/test_macros.h" @@ -28,11 +29,11 @@ namespace { class AxpySimpleTest : public ClientLibraryTestBase {}; TEST_F(AxpySimpleTest, AxTenValues) { - ComputationBuilder builder(client_, "ax_10"); + XlaBuilder builder("ax_10"); auto alpha = builder.ConstantR0(3.1415926535); auto x = builder.ConstantR1( {-1.0, 1.0, 2.0, -2.0, -3.0, 3.0, 4.0, -4.0, -5.0, 5.0}); - auto ax = builder.Mul(alpha, x); + builder.Mul(alpha, x); std::vector expected = { -3.14159265, 3.14159265, 6.28318531, -6.28318531, -9.42477796, @@ -46,7 +47,7 @@ XLA_TEST_F(AxpySimpleTest, AxpyZeroValues) { auto x = builder.ConstantR1({}); auto y = builder.ConstantR1({}); auto ax = builder.Mul(alpha, x); - auto axpy = builder.Add(ax, y); + builder.Add(ax, y); std::vector expected = {}; ComputeAndCompareR1(&builder, expected, {}, ErrorSpec(0.0001)); @@ -60,7 +61,7 @@ TEST_F(AxpySimpleTest, AxpyTenValues) { auto y = builder.ConstantR1( {5.0, -5.0, -4.0, 4.0, 3.0, -3.0, -2.0, 2.0, 1.0, -1.0}); auto ax = builder.Mul(alpha, x); - auto axpy = builder.Add(ax, y); + builder.Add(ax, y); TF_ASSERT_OK_AND_ASSIGN(ProgramShape shape, builder.GetProgramShape()); diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.cc b/tensorflow/compiler/xla/tests/client_library_test_base.cc index a677986cd9..3cae51576f 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.cc +++ b/tensorflow/compiler/xla/tests/client_library_test_base.cc @@ -95,6 +95,20 @@ StatusOr> ClientLibraryTestBase::ExecuteAndTransfer( &execution_options); } +StatusOr> ClientLibraryTestBase::ExecuteAndTransfer( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const Shape* shape_with_output_layout) { + ExecutionOptions execution_options = execution_options_; + if (shape_with_output_layout != nullptr) { + *execution_options.mutable_shape_with_output_layout() = + *shape_with_output_layout; + } + return client_->ExecuteAndTransfer(computation, arguments, + &execution_options); +} + +template <> StatusOr> ClientLibraryTestBase::ExecuteAndTransfer( ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments, @@ -104,6 +118,15 @@ StatusOr> ClientLibraryTestBase::ExecuteAndTransfer( return ExecuteAndTransfer(computation, arguments, shape_with_output_layout); } +template <> +StatusOr> ClientLibraryTestBase::ExecuteAndTransfer( + XlaBuilder* builder, tensorflow::gtl::ArraySlice arguments, + const Shape* shape_with_output_layout) { + // Build the computation, as a convenience. + TF_ASSIGN_OR_RETURN(auto computation, builder->Build()); + return ExecuteAndTransfer(computation, arguments, shape_with_output_layout); +} + std::unique_ptr ClientLibraryTestBase::ExecuteOrDie( ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments) { @@ -142,16 +165,18 @@ void ClientLibraryTestBase::ComputeAndCompareR1( arguments); } +template void ClientLibraryTestBase::ComputeAndCompareLiteral( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, const Shape* shape_with_layout) { EXPECT_IS_OK(ComputeAndCompareLiteralWithStatus(builder, expected, arguments, shape_with_layout)); } +template void ClientLibraryTestBase::ComputeAndCompareLiteral( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error, const Shape* shape_with_layout) { EXPECT_IS_OK(ComputeAndCompareLiteralWithStatus(builder, expected, arguments, @@ -249,8 +274,28 @@ ClientLibraryTestBase::ComputeAndCompareLiteralWithAllInputLayouts( return choose(0); } +tensorflow::Status +ClientLibraryTestBase::ComputeAndCompareLiteralWithAllOutputLayouts( + const xla::XlaComputation& /*computation*/, const Literal& /*expected*/, + tensorflow::gtl::ArraySlice /*arguments*/, + const std::function& /*verify_output*/) { + return Unimplemented("not yet implemented for XlaComputation"); +} + +tensorflow::Status +ClientLibraryTestBase::ComputeAndCompareLiteralWithAllInputLayouts( + const xla::XlaComputation& /*computation*/, const Literal& /*expected*/, + tensorflow::gtl::ArraySlice /*arguments*/, + const std::function& /*verify_output*/, + const Shape* /*output_with_layout*/) { + return Unimplemented("not yet implemented for XlaComputation"); +} + +template tensorflow::Status ClientLibraryTestBase::ComputeAndCompareLiteralWithStatus( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments_passed_in, const Shape* shape_with_layout) { std::vector arguments(arguments_passed_in.begin(), @@ -307,8 +352,9 @@ tensorflow::Status ClientLibraryTestBase::ComputeAndCompareLiteralWithStatus( return tensorflow::Status::OK(); } +template tensorflow::Status ClientLibraryTestBase::ComputeAndCompareLiteralWithStatus( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments_passed_in, ErrorSpec error, const Shape* shape_with_layout) { std::vector arguments(arguments_passed_in.begin(), @@ -563,4 +609,24 @@ ComputationDataHandle ClientLibraryTestBase::CreateConstantFromLiteral( use_bfloat16_ ? *LiteralTestUtil::ConvertF32ToBF16(literal) : literal); } +template void ClientLibraryTestBase::ComputeAndCompareLiteral( + ComputationBuilder* builder, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, + const Shape* shape_with_layout); + +template void ClientLibraryTestBase::ComputeAndCompareLiteral( + XlaBuilder* builder, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, + const Shape* shape_with_layout); + +template void ClientLibraryTestBase::ComputeAndCompareLiteral( + ComputationBuilder* builder, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, ErrorSpec error, + const Shape* shape_with_layout); + +template void ClientLibraryTestBase::ComputeAndCompareLiteral( + XlaBuilder* builder, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, ErrorSpec error, + const Shape* shape_with_layout); + } // namespace xla diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.h b/tensorflow/compiler/xla/tests/client_library_test_base.h index ba0319990b..b553beb01a 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.h +++ b/tensorflow/compiler/xla/tests/client_library_test_base.h @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation.h" #include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/client/global_data.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/ptr_util.h" #include "tensorflow/compiler/xla/statusor.h" @@ -94,15 +95,22 @@ class ClientLibraryTestBase : public ::testing::Test { StatusOr> Execute( ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments); + + template StatusOr> ExecuteAndTransfer( - ComputationBuilder* builder, - tensorflow::gtl::ArraySlice arguments, + BuilderT* builder, tensorflow::gtl::ArraySlice arguments, const Shape* shape_with_output_layout = nullptr); + StatusOr> ExecuteAndTransfer( const Computation& computation, tensorflow::gtl::ArraySlice arguments, const Shape* shape_with_output_layout = nullptr); + StatusOr> ExecuteAndTransfer( + const XlaComputation& computation, + tensorflow::gtl::ArraySlice arguments, + const Shape* shape_with_output_layout = nullptr); + // Convenience OrDie variants of above methods. std::unique_ptr ExecuteOrDie( ComputationBuilder* builder, @@ -130,12 +138,12 @@ class ClientLibraryTestBase : public ::testing::Test { tensorflow::gtl::ArraySlice arguments, ErrorSpec error); - template - void ComputeAndCompareR1(ComputationBuilder* builder, + template + void ComputeAndCompareR1(BuilderT* builder, tensorflow::gtl::ArraySlice expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR1(ComputationBuilder* builder, + template + void ComputeAndCompareR1(BuilderT* builder, tensorflow::gtl::ArraySlice expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error); @@ -179,22 +187,26 @@ class ClientLibraryTestBase : public ::testing::Test { // Build and run the computation and compare the result with the given // literal. shape_with_layout indicates the result layout to request when // calling Execute. + template void ComputeAndCompareLiteral( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, const Shape* shape_with_layout = nullptr); + template void ComputeAndCompareLiteral( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error, const Shape* shape_with_layout = nullptr); // ComputeAndCompare variant which returns an error status. + template tensorflow::Status ComputeAndCompareLiteralWithStatus( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, const Shape* shape_with_layout = nullptr); + template tensorflow::Status ComputeAndCompareLiteralWithStatus( - ComputationBuilder* builder, const Literal& expected, + BuilderT* builder, const Literal& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error, const Shape* shape_with_layout = nullptr); @@ -399,6 +411,18 @@ class ClientLibraryTestBase : public ::testing::Test { const string& error_message)>& verify_output, const Shape* output_with_layout = nullptr); + tensorflow::Status ComputeAndCompareLiteralWithAllOutputLayouts( + const xla::XlaComputation& computation, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, + const std::function& verify_output); + tensorflow::Status ComputeAndCompareLiteralWithAllInputLayouts( + const xla::XlaComputation& computation, const Literal& expected, + tensorflow::gtl::ArraySlice arguments, + const std::function& verify_output, + const Shape* output_with_layout = nullptr); + // Executes the computation and calculates the expected reference value using // the HloEvaluator. Returns two literal in the order of (expected, actual). StatusOr, std::unique_ptr>> @@ -440,9 +464,9 @@ void ClientLibraryTestBase::ComputeAndCompareR0( arguments, error); } -template +template void ClientLibraryTestBase::ComputeAndCompareR1( - ComputationBuilder* builder, tensorflow::gtl::ArraySlice expected, + BuilderT* builder, tensorflow::gtl::ArraySlice expected, tensorflow::gtl::ArraySlice arguments) { std::unique_ptr expected_literal = Literal::CreateR1(expected); @@ -450,9 +474,9 @@ void ClientLibraryTestBase::ComputeAndCompareR1( arguments); } -template +template void ClientLibraryTestBase::ComputeAndCompareR1( - ComputationBuilder* builder, tensorflow::gtl::ArraySlice expected, + BuilderT* builder, tensorflow::gtl::ArraySlice expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error) { static_assert(std::is_same::value || std::is_same::value || -- GitLab From 2219b88a3d5154b9158a1902b061cad6cae2d0a8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 14:00:39 -0700 Subject: [PATCH 169/906] Fix behavior of bucket_by_sequence_length with tuple Dataset elements Fixes #17932 PiperOrigin-RevId: 190270732 --- .../python/kernel_tests/bucketing_test.py | 25 +++++++++++++++++++ .../contrib/data/python/ops/grouping.py | 4 +-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/data/python/kernel_tests/bucketing_test.py b/tensorflow/contrib/data/python/kernel_tests/bucketing_test.py index 94f800e8a5..d0131896a1 100644 --- a/tensorflow/contrib/data/python/kernel_tests/bucketing_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/bucketing_test.py @@ -468,6 +468,31 @@ class BucketBySequenceLength(test.TestCase): self.assertEqual(sorted(batch_sizes), sorted(batch_sizes_val)) self.assertEqual(sorted(boundaries), sorted(lengths_val)) + def testTupleElements(self): + + def elements_gen(): + text = [[1, 2, 3], [3, 4, 5, 6, 7], [1, 2], [8, 9, 0, 2, 3]] + label = [1, 2, 1, 2] + for x, y in zip(text, label): + yield (x, y) + + def element_length_fn(x, y): + del y + return array_ops.shape(x)[0] + + dataset = dataset_ops.Dataset.from_generator( + generator=elements_gen, + output_shapes=(tensor_shape.TensorShape([None]), + tensor_shape.TensorShape([])), + output_types=(dtypes.int32, dtypes.int32)) + dataset = dataset.apply(grouping.bucket_by_sequence_length( + element_length_func=element_length_fn, + bucket_batch_sizes=[2, 2, 2], + bucket_boundaries=[0, 8])) + shapes = dataset.output_shapes + self.assertEqual([None, None], shapes[0].as_list()) + self.assertEqual([None], shapes[1].as_list()) + if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/data/python/ops/grouping.py b/tensorflow/contrib/data/python/ops/grouping.py index ae10d2eb22..36591c055a 100644 --- a/tensorflow/contrib/data/python/ops/grouping.py +++ b/tensorflow/contrib/data/python/ops/grouping.py @@ -140,9 +140,9 @@ def bucket_by_sequence_length(element_length_func, batch_sizes = constant_op.constant(bucket_batch_sizes, dtype=dtypes.int64) - def element_to_bucket_id(element): + def element_to_bucket_id(*args): """Return int64 id of the length bucket for this element.""" - seq_length = element_length_func(element) + seq_length = element_length_func(*args) boundaries = list(bucket_boundaries) buckets_min = [np.iinfo(np.int32).min] + boundaries -- GitLab From 13ef0af4867477cdda7e0b294e61560c2952df42 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Fri, 23 Mar 2018 14:19:37 -0700 Subject: [PATCH 170/906] Fix buffer overflow when fetching resources. PiperOrigin-RevId: 190273682 --- .../python/kernel_tests/resource_variable_ops_test.py | 6 ++++++ tensorflow/python/lib/core/ndarray_tensor.cc | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/resource_variable_ops_test.py b/tensorflow/python/kernel_tests/resource_variable_ops_test.py index 2dc993f811..563eeff2a6 100644 --- a/tensorflow/python/kernel_tests/resource_variable_ops_test.py +++ b/tensorflow/python/kernel_tests/resource_variable_ops_test.py @@ -103,6 +103,12 @@ class ResourceVariableOpsTest(test_util.TensorFlowTestCase): v = resource_variable_ops.ResourceVariable(False, name="bool_test") self.assertAllEqual(bool(v), False) + def testFetchHandle(self): + with self.test_session(): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1], name="foo") + self.assertGreater(len(handle.eval()), 0) + def testAssignVariableDtypeMismatchEager(self): with context.eager_mode(): handle = resource_variable_ops.var_handle_op( diff --git a/tensorflow/python/lib/core/ndarray_tensor.cc b/tensorflow/python/lib/core/ndarray_tensor.cc index 994af69386..a07e305ffb 100644 --- a/tensorflow/python/lib/core/ndarray_tensor.cc +++ b/tensorflow/python/lib/core/ndarray_tensor.cc @@ -267,7 +267,9 @@ gtl::InlinedVector GetPyArrayDimensionsForTensor( const int ndims = TF_NumDims(tensor); gtl::InlinedVector dims(ndims); if (TF_TensorType(tensor) == TF_RESOURCE) { - dims[0] = TF_TensorByteSize(tensor); + CHECK_EQ(ndims, 0) + << "Fetching of non-scalar resource tensors is not supported."; + dims.push_back(TF_TensorByteSize(tensor)); *nelems = dims[0]; } else { *nelems = 1; -- GitLab From 18832ec8497a6acc6f828808e5ea3a2859548efa Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 14:41:02 -0700 Subject: [PATCH 171/906] Benchmarker for LPIRC CVPR2018. PiperOrigin-RevId: 190276899 --- .../org/tensorflow/ovic/OvicBenchmarker.java | 197 ++++ .../org/tensorflow/ovic/OvicClassifier.java | 209 ++++ .../ovic/OvicSingleImageResult.java | 54 + .../tensorflow/ovic/OvicClassifierTest.java | 176 +++ .../lite/java/ovic/src/testdata/labels.txt | 1001 +++++++++++++++++ 5 files changed, 1637 insertions(+) create mode 100644 tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicBenchmarker.java create mode 100644 tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicClassifier.java create mode 100644 tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicSingleImageResult.java create mode 100644 tensorflow/contrib/lite/java/ovic/src/test/java/org/tensorflow/ovic/OvicClassifierTest.java create mode 100644 tensorflow/contrib/lite/java/ovic/src/testdata/labels.txt diff --git a/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicBenchmarker.java b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicBenchmarker.java new file mode 100644 index 0000000000..d0102883e6 --- /dev/null +++ b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicBenchmarker.java @@ -0,0 +1,197 @@ +/*Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +package org.tensorflow.ovic; + +import android.graphics.Bitmap; +import android.os.SystemClock; +import android.util.Log; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; + +/** + * Class that benchmarks image classifier models. + * + *

===================== General workflow ======================= + * + *

{@code
+ * benchmarker = new OvicBenchmarker();
+ * benchmarker.getReadyToTest(labelInputStream, model);
+ * while (!benchmarker.shouldStop()) {
+ *   Bitmap bitmap = ...
+ *   benchmarker.doTestIteration(bitmap);
+ * }
+ * }
+ */ +public class OvicBenchmarker { + /** Tag for the {@link Log}. */ + private static final String TAG = "OvicBenchmarker"; + + /** Evaluation transformation parameters. */ + private static final float CENTRAL_FRACTION = 0.875f; + + /** Dimensions of inputs. */ + private static final int DIM_BATCH_SIZE = 1; + private static final int DIM_PIXEL_SIZE = 3; + private int imgHeight = 224; + private int imgWidth = 224; + + /* Preallocated buffers for storing image data in. */ + private int[] intValues = null; + + /** A ByteBuffer to hold image data, to be feed into classifier as inputs. */ + private ByteBuffer imgData = null; + + private OvicClassifier classifier; + + /** Total runtime in ms. */ + private double totalRuntime = 0.0; + /** Total allowed runtime in ms. */ + private double wallTime = 20000 * 30.0; + + private Boolean benchmarkStarted = null; + + /** + * Initializes an {@link OvicBenchmarker} + * + * @param wallTime: a double number specifying the total amount of time to benchmark. + */ + public OvicBenchmarker(double wallTime) { + benchmarkStarted = false; + totalRuntime = 0.0; + this.wallTime = wallTime; + } + + /** Check whether the benchmarker should stop. */ + public Boolean shouldStop() { + if (totalRuntime >= wallTime) { + Log.e( + TAG, + "Total runtime " + + Double.toString(totalRuntime) + + " exceeded walltime " + + Double.toString(wallTime)); + return true; + } + return false; + } + + /** Check whether the benchmarker is ready to start classifying images. */ + public Boolean readyToTest() { + return (classifier != null); + } + + /** + * Getting the benchmarker ready for classifying images. + * + * @param labelInputStream: an {@link InputStream} specifying where the list of labels should be + * read from. + * @param model: a {@link MappedByteBuffer} model to benchmark. + */ + public void getReadyToTest(InputStream labelInputStream, MappedByteBuffer model) { + try { + Log.i(TAG, "Creating classifier."); + classifier = new OvicClassifier(labelInputStream, model); + int [] inputDims = classifier.getInputDims(); + imgHeight = inputDims[1]; + imgWidth = inputDims[2]; + // Only accept QUANTIZED_UINT8 input. + imgData = ByteBuffer.allocateDirect(DIM_BATCH_SIZE * imgHeight * imgWidth * DIM_PIXEL_SIZE); + imgData.order(ByteOrder.nativeOrder()); + intValues = new int[imgHeight * imgWidth]; + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + Log.e(TAG, "Failed to initialize ImageNet classifier for the benchmarker."); + } + } + + /** Return how many classes are predicted per image. */ + public int getNumPredictions() { + return classifier.getNumPredictions(); + } + + /** + * Perform test on a single bitmap image. + * + * @param bitmap: a {@link Bitmap} image to classify. + */ + public OvicSingleImageResult doTestIteration(Bitmap bitmap) + throws IOException, InterruptedException { + if (shouldStop() || !readyToTest()) { + return null; + } + OvicSingleImageResult iterResult = null; + try { + Log.i(TAG, "Converting bitmap."); + convertBitmapToInput(bitmap); + Log.i(TAG, "Classifying image."); + iterResult = classifier.classifyByteBuffer(imgData); + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage()); + Log.e(TAG, "Failed to classify image."); + } + if (iterResult == null || iterResult.latency == null) { + throw new RuntimeException("Classification result or timing is invalid."); + } + Log.d(TAG, "Native inference latency: " + iterResult.latency); + Log.i(TAG, iterResult.toString()); + + if (!benchmarkStarted) { // Skip the first image to discount warming-up time. + benchmarkStarted = true; + } else { + totalRuntime += (double) iterResult.latency; + } + return iterResult; + } + + /** + * Writes Image data into a {@link ByteBuffer}. + * + * @param bitmap: a {@link Bitmap} source image. + */ + private void convertBitmapToInput(Bitmap bitmap) throws RuntimeException { + if (imgData == null) { + throw new RuntimeException("Benchmarker is not yet ready to test."); + } + imgData.rewind(); + // Perform transformations corresponding to evaluation mode. + float width = (float) bitmap.getWidth(); + float height = (float) bitmap.getHeight(); + int stWidth = Math.round((width - width * CENTRAL_FRACTION) / 2); + int stHeight = Math.round((height - height * CENTRAL_FRACTION) / 2); + int newWidth = Math.round(width - stWidth * 2); + int newHeight = Math.round(height - stHeight * 2); + bitmap = Bitmap.createBitmap(bitmap, stWidth, stHeight, newWidth, newHeight); + bitmap = Bitmap.createScaledBitmap(bitmap, imgWidth, imgHeight, true); + bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + + // Convert the image to ByteBuffer. + int pixel = 0; + long startTime = SystemClock.uptimeMillis(); + + for (int i = 0; i < imgHeight; ++i) { + for (int j = 0; j < imgWidth; ++j) { + final int val = intValues[pixel++]; + imgData.put((byte) ((val >> 16) & 0xFF)); + imgData.put((byte) ((val >> 8) & 0xFF)); + imgData.put((byte) (val & 0xFF)); + } + } + long endTime = SystemClock.uptimeMillis(); + Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime)); + } +} diff --git a/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicClassifier.java b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicClassifier.java new file mode 100644 index 0000000000..b2dfd8f2e7 --- /dev/null +++ b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicClassifier.java @@ -0,0 +1,209 @@ +/*Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +package org.tensorflow.ovic; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import org.tensorflow.lite.Interpreter; +import org.tensorflow.lite.TestHelper; + +/** Benchmark ImageNet Classifier with Tensorflow Lite. */ +public class OvicClassifier { + + /** Tag for the {@link Log}. */ + private static final String TAG = "OvicClassifier"; + + /** Number of results to show (i.e. the "K" in top-K predictions). */ + private static final int RESULTS_TO_SHOW = 5; + + /** An instance of the driver class to run model inference with Tensorflow Lite. */ + private Interpreter tflite; + + /** Labels corresponding to the output of the vision model. */ + private List labelList; + + /** An array to hold inference results, to be feed into Tensorflow Lite as outputs. */ + private byte[][] inferenceOutputArray = null; + /** An array to hold final prediction probabilities. */ + private float[][] labelProbArray = null; + + /** Input resultion. */ + private int[] inputDims = null; + /** Whether the model runs as float or quantized. */ + private Boolean outputIsFloat = null; + + private PriorityQueue> sortedLabels = + new PriorityQueue<>( + RESULTS_TO_SHOW, + new Comparator>() { + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + return (o1.getValue()).compareTo(o2.getValue()); + } + }); + + /** Initializes an {@code OvicClassifier}. */ + OvicClassifier(InputStream labelInputStream, MappedByteBuffer model) + throws IOException, RuntimeException { + if (model == null) { + throw new RuntimeException("Input model is empty."); + } + labelList = loadLabelList(labelInputStream); + // OVIC uses one thread for CPU inference. + tflite = new Interpreter(model, 1); + inputDims = TestHelper.getInputDims(tflite, 0); + if (inputDims.length != 4) { + throw new RuntimeException("The model's input dimensions must be 4 (BWHC)."); + } + if (inputDims[0] != 1) { + throw new RuntimeException("The model must have a batch size of 1, got " + + inputDims[0] + " instead."); + } + if (inputDims[3] != 3) { + throw new RuntimeException("The model must have three color channels, got " + + inputDims[3] + " instead."); + } + int minSide = Math.min(inputDims[1], inputDims[2]); + int maxSide = Math.max(inputDims[1], inputDims[2]); + if (minSide <= 0 || maxSide > 1000) { + throw new RuntimeException("The model's resolution must be between (0, 1000]."); + } + String outputDataType = TestHelper.getOutputDataType(tflite, 0); + if (outputDataType.equals("float")) { + outputIsFloat = true; + } else if (outputDataType.equals("byte")) { + outputIsFloat = false; + } else { + throw new RuntimeException("Cannot process output type: " + outputDataType); + } + inferenceOutputArray = new byte[1][labelList.size()]; + labelProbArray = new float[1][labelList.size()]; + } + + /** Classifies a {@link ByteBuffer} image. */ + // @throws RuntimeException if model is uninitialized. + OvicSingleImageResult classifyByteBuffer(ByteBuffer imgData) throws RuntimeException { + if (tflite == null) { + throw new RuntimeException(TAG + ": ImageNet classifier has not been initialized; Failed."); + } + if (outputIsFloat == null) { + throw new RuntimeException(TAG + ": Classifier output type has not been resolved."); + } + if (outputIsFloat) { + tflite.run(imgData, labelProbArray); + } else { + tflite.run(imgData, inferenceOutputArray); + /** Convert results to float */ + for (int i = 0; i < inferenceOutputArray[0].length; i++) { + labelProbArray[0][i] = (inferenceOutputArray[0][i] & 0xff) / 255.0f; + } + } + OvicSingleImageResult iterResult = computeTopKLabels(); + iterResult.latency = getLastNativeInferenceLatencyMilliseconds(); + return iterResult; + } + + /** Return the probability array of all classes. */ + public float[][] getlabelProbArray() { + return labelProbArray; + } + + /** Return the number of top labels predicted by the classifier. */ + public int getNumPredictions() { + return RESULTS_TO_SHOW; + } + + /** Return the four dimensions of the input image. */ + public int[] getInputDims() { + return inputDims; + } + + /* + * Get native inference latency of last image classification run. + * @throws RuntimeException if model is uninitialized. + */ + public Long getLastNativeInferenceLatencyMilliseconds() { + if (tflite == null) { + throw new RuntimeException(TAG + ": ImageNet classifier has not been initialized; Failed."); + } + Long latency = tflite.getLastNativeInferenceDurationNanoseconds(); + return (latency == null) ? null : (Long) (latency / 1000000); + } + + /** Closes tflite to release resources. */ + public void close() { + tflite.close(); + tflite = null; + } + + /** Reads label list from Assets. */ + private static List loadLabelList(InputStream labelInputStream) throws IOException { + List labelList = new ArrayList(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(labelInputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + labelList.add(line); + } + } + return labelList; + } + + /** Computes top-K labels. */ + private OvicSingleImageResult computeTopKLabels() { + if (labelList == null) { + throw new RuntimeException("Label file has not been loaded."); + } + for (int i = 0; i < labelList.size(); ++i) { + sortedLabels.add(new AbstractMap.SimpleEntry<>(i, labelProbArray[0][i])); + if (sortedLabels.size() > RESULTS_TO_SHOW) { + sortedLabels.poll(); + } + } + OvicSingleImageResult singleImageResult = new OvicSingleImageResult(); + if (sortedLabels.size() != RESULTS_TO_SHOW) { + throw new RuntimeException( + "Number of returned labels does not match requirement: " + + sortedLabels.size() + + " returned, but " + + RESULTS_TO_SHOW + + " required."); + } + for (int i = 0; i < RESULTS_TO_SHOW; ++i) { + Map.Entry label = sortedLabels.poll(); + // ImageNet model prediction indices are 0-based. + singleImageResult.topKIndices.add(label.getKey()); + singleImageResult.topKClasses.add(labelList.get(label.getKey())); + singleImageResult.topKProbs.add(label.getValue()); + } + // Labels with lowest probability are returned first, hence need to reverse them. + Collections.reverse(singleImageResult.topKIndices); + Collections.reverse(singleImageResult.topKClasses); + Collections.reverse(singleImageResult.topKProbs); + return singleImageResult; + } +} diff --git a/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicSingleImageResult.java b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicSingleImageResult.java new file mode 100644 index 0000000000..4af9a65c2f --- /dev/null +++ b/tensorflow/contrib/lite/java/ovic/src/main/java/org/tensorflow/ovic/OvicSingleImageResult.java @@ -0,0 +1,54 @@ +/*Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +package org.tensorflow.ovic; + +import java.util.ArrayList; + +/** Result class for inference run on a single image. */ +public class OvicSingleImageResult { + + /** Top K classes and probabilities. */ + public ArrayList topKClasses; + public ArrayList topKProbs; + public ArrayList topKIndices; + + /** Latency (ms). */ + public Long latency; + + OvicSingleImageResult() { + topKClasses = new ArrayList<>(); + topKProbs = new ArrayList<>(); + topKIndices = new ArrayList<>(); + latency = -1L; + } + + @Override + public String toString() { + String textToShow = latency + "ms"; + for (int k = 0; k < topKProbs.size(); ++k) { + textToShow += + "\nPrediction [" + + k + + "] = Class " + + Integer.toString(topKIndices.get(k)) + + " (" + + topKClasses.get(k) + + ") : " + + Float.toString(topKProbs.get(k)); + } + return textToShow; + } + +} diff --git a/tensorflow/contrib/lite/java/ovic/src/test/java/org/tensorflow/ovic/OvicClassifierTest.java b/tensorflow/contrib/lite/java/ovic/src/test/java/org/tensorflow/ovic/OvicClassifierTest.java new file mode 100644 index 0000000000..4fd23a99d2 --- /dev/null +++ b/tensorflow/contrib/lite/java/ovic/src/test/java/org/tensorflow/ovic/OvicClassifierTest.java @@ -0,0 +1,176 @@ +/*Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +package org.tensorflow.ovic; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import javax.imageio.ImageIO; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link org.tensorflow.ovic.OvicClassifier}. */ +@RunWith(JUnit4.class) +public final class OvicClassifierTest { + + private OvicClassifier classifier; + private InputStream labelsInputStream = null; + private MappedByteBuffer quantizedModel = null; + private MappedByteBuffer floatModel = null; + private MappedByteBuffer lowResModel = null; + private ByteBuffer testImage = null; + private ByteBuffer lowResTestImage = null; + private OvicSingleImageResult testResult = null; + private static final String LABELS_PATH = "testdata/labels.txt"; + private static final String QUANTIZED_MODEL_PATH = "testdata/quantized_model.lite"; + private static final String LOW_RES_MODEL_PATH = "testdata/low_res_model.lite"; + private static final String FLOAT_MODEL_PATH = "testdata/float_model.lite"; + private static final String TEST_IMAGE_PATH = "testdata/test_image_224.jpg"; + private static final String TEST_LOW_RES_IMAGE_PATH = "testdata/test_image_128.jpg"; + private static final int TEST_IMAGE_GROUNDTRUTH = 653; // "military uniform" + + @Before + public void setUp() { + try { + File labelsfile = new File(getTestDir(LABELS_PATH)); + labelsInputStream = new FileInputStream(labelsfile); + quantizedModel = loadModelFile(getTestDir(QUANTIZED_MODEL_PATH)); + floatModel = loadModelFile(getTestDir(FLOAT_MODEL_PATH)); + lowResModel = loadModelFile(getTestDir(LOW_RES_MODEL_PATH)); + File imageFile = new File(getTestDir(TEST_IMAGE_PATH)); + BufferedImage img = ImageIO.read(imageFile); + testImage = toByteBuffer(img); + // Low res image and models. + imageFile = new File(getTestDir(TEST_LOW_RES_IMAGE_PATH)); + img = ImageIO.read(imageFile); + lowResTestImage = toByteBuffer(img); + } catch (IOException e) { + System.out.print(e.getMessage()); + } + System.out.println("Successful setup"); + } + + private static String getTestDir(String testfile) throws IOException { + return Paths.get("third_party/tensorflow/contrib/lite/java/ovic/src/", testfile).toString(); + } + + @Test + public void ovicClassifier_quantizedModelCreateSuccess() throws Exception { + classifier = new OvicClassifier(labelsInputStream, quantizedModel); + assertThat(classifier != null).isTrue(); + } + + @Test + public void ovicClassifier_floatModelCreateSuccess() throws Exception { + classifier = new OvicClassifier(labelsInputStream, floatModel); + assertThat(classifier != null).isTrue(); + } + + @Test + public void ovicClassifier_quantizedModelClassifySuccess() throws Exception { + classifier = new OvicClassifier(labelsInputStream, quantizedModel); + testResult = classifier.classifyByteBuffer(testImage); + assertCorrectTopK(testResult); + } + + @Test + public void ovicClassifier_floatModelClassifySuccess() throws Exception { + classifier = new OvicClassifier(labelsInputStream, floatModel); + testResult = classifier.classifyByteBuffer(testImage); + assertCorrectTopK(testResult); + } + + @Test + public void ovicClassifier_lowResModelClassifySuccess() throws Exception { + classifier = new OvicClassifier(labelsInputStream, lowResModel); + testResult = classifier.classifyByteBuffer(lowResTestImage); + assertCorrectTopK(testResult); + } + + @Test + public void ovicClassifier_latencyNotNull() throws Exception { + classifier = new OvicClassifier(labelsInputStream, floatModel); + testResult = classifier.classifyByteBuffer(testImage); + assertThat(testResult.latency != null).isTrue(); + } + + @Test + public void ovicClassifier_mismatchedInputResolutionFails() throws Exception { + classifier = new OvicClassifier(labelsInputStream, lowResModel); + int[] inputDims = classifier.getInputDims(); + assertThat((inputDims[1] == 128) && (inputDims[2] == 128)).isTrue(); + try { + testResult = classifier.classifyByteBuffer(testImage); + fail(); + } catch (RuntimeException e) { + assertThat(e) + .hasMessageThat() + .contains( + "Failed to get input dimensions. 0-th input should have 49152 bytes, " + + "but found 150528 bytes."); + } + } + + private static ByteBuffer toByteBuffer(BufferedImage image) { + ByteBuffer imgData = ByteBuffer.allocateDirect( + image.getHeight() * image.getWidth() * 3); + imgData.order(ByteOrder.nativeOrder()); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int val = image.getRGB(x, y); + imgData.put((byte) ((val >> 16) & 0xFF)); + imgData.put((byte) ((val >> 8) & 0xFF)); + imgData.put((byte) (val & 0xFF)); + } + } + return imgData; + } + + private static void assertCorrectTopK(OvicSingleImageResult testResult) { + assertThat(testResult.topKClasses.size() > 0).isTrue(); + Boolean topKAccurate = false; + // Assert that the correct class is in the top K. + for (int i = 0; i < testResult.topKIndices.size(); i++) { + if (testResult.topKIndices.get(i) == TEST_IMAGE_GROUNDTRUTH) { + topKAccurate = true; + break; + } + } + System.out.println(testResult.toString()); + System.out.flush(); + assertThat(topKAccurate).isTrue(); + } + + private static MappedByteBuffer loadModelFile(String modelFilePath) throws IOException { + File modelfile = new File(modelFilePath); + FileInputStream inputStream = new FileInputStream(modelfile); + FileChannel fileChannel = inputStream.getChannel(); + long startOffset = 0L; + long declaredLength = fileChannel.size(); + return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); + } +} diff --git a/tensorflow/contrib/lite/java/ovic/src/testdata/labels.txt b/tensorflow/contrib/lite/java/ovic/src/testdata/labels.txt new file mode 100644 index 0000000000..fe811239d8 --- /dev/null +++ b/tensorflow/contrib/lite/java/ovic/src/testdata/labels.txt @@ -0,0 +1,1001 @@ +background +tench +goldfish +great white shark +tiger shark +hammerhead +electric ray +stingray +cock +hen +ostrich +brambling +goldfinch +house finch +junco +indigo bunting +robin +bulbul +jay +magpie +chickadee +water ouzel +kite +bald eagle +vulture +great grey owl +European fire salamander +common newt +eft +spotted salamander +axolotl +bullfrog +tree frog +tailed frog +loggerhead +leatherback turtle +mud turtle +terrapin +box turtle +banded gecko +common iguana +American chameleon +whiptail +agama +frilled lizard +alligator lizard +Gila monster +green lizard +African chameleon +Komodo dragon +African crocodile +American alligator +triceratops +thunder snake +ringneck snake +hognose snake +green snake +king snake +garter snake +water snake +vine snake +night snake +boa constrictor +rock python +Indian cobra +green mamba +sea snake +horned viper +diamondback +sidewinder +trilobite +harvestman +scorpion +black and gold garden spider +barn spider +garden spider +black widow +tarantula +wolf spider +tick +centipede +black grouse +ptarmigan +ruffed grouse +prairie chicken +peacock +quail +partridge +African grey +macaw +sulphur-crested cockatoo +lorikeet +coucal +bee eater +hornbill +hummingbird +jacamar +toucan +drake +red-breasted merganser +goose +black swan +tusker +echidna +platypus +wallaby +koala +wombat +jellyfish +sea anemone +brain coral +flatworm +nematode +conch +snail +slug +sea slug +chiton +chambered nautilus +Dungeness crab +rock crab +fiddler crab +king crab +American lobster +spiny lobster +crayfish +hermit crab +isopod +white stork +black stork +spoonbill +flamingo +little blue heron +American egret +bittern +crane +limpkin +European gallinule +American coot +bustard +ruddy turnstone +red-backed sandpiper +redshank +dowitcher +oystercatcher +pelican +king penguin +albatross +grey whale +killer whale +dugong +sea lion +Chihuahua +Japanese spaniel +Maltese dog +Pekinese +Shih-Tzu +Blenheim spaniel +papillon +toy terrier +Rhodesian ridgeback +Afghan hound +basset +beagle +bloodhound +bluetick +black-and-tan coonhound +Walker hound +English foxhound +redbone +borzoi +Irish wolfhound +Italian greyhound +whippet +Ibizan hound +Norwegian elkhound +otterhound +Saluki +Scottish deerhound +Weimaraner +Staffordshire bullterrier +American Staffordshire terrier +Bedlington terrier +Border terrier +Kerry blue terrier +Irish terrier +Norfolk terrier +Norwich terrier +Yorkshire terrier +wire-haired fox terrier +Lakeland terrier +Sealyham terrier +Airedale +cairn +Australian terrier +Dandie Dinmont +Boston bull +miniature schnauzer +giant schnauzer +standard schnauzer +Scotch terrier +Tibetan terrier +silky terrier +soft-coated wheaten terrier +West Highland white terrier +Lhasa +flat-coated retriever +curly-coated retriever +golden retriever +Labrador retriever +Chesapeake Bay retriever +German short-haired pointer +vizsla +English setter +Irish setter +Gordon setter +Brittany spaniel +clumber +English springer +Welsh springer spaniel +cocker spaniel +Sussex spaniel +Irish water spaniel +kuvasz +schipperke +groenendael +malinois +briard +kelpie +komondor +Old English sheepdog +Shetland sheepdog +collie +Border collie +Bouvier des Flandres +Rottweiler +German shepherd +Doberman +miniature pinscher +Greater Swiss Mountain dog +Bernese mountain dog +Appenzeller +EntleBucher +boxer +bull mastiff +Tibetan mastiff +French bulldog +Great Dane +Saint Bernard +Eskimo dog +malamute +Siberian husky +dalmatian +affenpinscher +basenji +pug +Leonberg +Newfoundland +Great Pyrenees +Samoyed +Pomeranian +chow +keeshond +Brabancon griffon +Pembroke +Cardigan +toy poodle +miniature poodle +standard poodle +Mexican hairless +timber wolf +white wolf +red wolf +coyote +dingo +dhole +African hunting dog +hyena +red fox +kit fox +Arctic fox +grey fox +tabby +tiger cat +Persian cat +Siamese cat +Egyptian cat +cougar +lynx +leopard +snow leopard +jaguar +lion +tiger +cheetah +brown bear +American black bear +ice bear +sloth bear +mongoose +meerkat +tiger beetle +ladybug +ground beetle +long-horned beetle +leaf beetle +dung beetle +rhinoceros beetle +weevil +fly +bee +ant +grasshopper +cricket +walking stick +cockroach +mantis +cicada +leafhopper +lacewing +dragonfly +damselfly +admiral +ringlet +monarch +cabbage butterfly +sulphur butterfly +lycaenid +starfish +sea urchin +sea cucumber +wood rabbit +hare +Angora +hamster +porcupine +fox squirrel +marmot +beaver +guinea pig +sorrel +zebra +hog +wild boar +warthog +hippopotamus +ox +water buffalo +bison +ram +bighorn +ibex +hartebeest +impala +gazelle +Arabian camel +llama +weasel +mink +polecat +black-footed ferret +otter +skunk +badger +armadillo +three-toed sloth +orangutan +gorilla +chimpanzee +gibbon +siamang +guenon +patas +baboon +macaque +langur +colobus +proboscis monkey +marmoset +capuchin +howler monkey +titi +spider monkey +squirrel monkey +Madagascar cat +indri +Indian elephant +African elephant +lesser panda +giant panda +barracouta +eel +coho +rock beauty +anemone fish +sturgeon +gar +lionfish +puffer +abacus +abaya +academic gown +accordion +acoustic guitar +aircraft carrier +airliner +airship +altar +ambulance +amphibian +analog clock +apiary +apron +ashcan +assault rifle +backpack +bakery +balance beam +balloon +ballpoint +Band Aid +banjo +bannister +barbell +barber chair +barbershop +barn +barometer +barrel +barrow +baseball +basketball +bassinet +bassoon +bathing cap +bath towel +bathtub +beach wagon +beacon +beaker +bearskin +beer bottle +beer glass +bell cote +bib +bicycle-built-for-two +bikini +binder +binoculars +birdhouse +boathouse +bobsled +bolo tie +bonnet +bookcase +bookshop +bottlecap +bow +bow tie +brass +brassiere +breakwater +breastplate +broom +bucket +buckle +bulletproof vest +bullet train +butcher shop +cab +caldron +candle +cannon +canoe +can opener +cardigan +car mirror +carousel +carpenter's kit +carton +car wheel +cash machine +cassette +cassette player +castle +catamaran +CD player +cello +cellular telephone +chain +chainlink fence +chain mail +chain saw +chest +chiffonier +chime +china cabinet +Christmas stocking +church +cinema +cleaver +cliff dwelling +cloak +clog +cocktail shaker +coffee mug +coffeepot +coil +combination lock +computer keyboard +confectionery +container ship +convertible +corkscrew +cornet +cowboy boot +cowboy hat +cradle +crane +crash helmet +crate +crib +Crock Pot +croquet ball +crutch +cuirass +dam +desk +desktop computer +dial telephone +diaper +digital clock +digital watch +dining table +dishrag +dishwasher +disk brake +dock +dogsled +dome +doormat +drilling platform +drum +drumstick +dumbbell +Dutch oven +electric fan +electric guitar +electric locomotive +entertainment center +envelope +espresso maker +face powder +feather boa +file +fireboat +fire engine +fire screen +flagpole +flute +folding chair +football helmet +forklift +fountain +fountain pen +four-poster +freight car +French horn +frying pan +fur coat +garbage truck +gasmask +gas pump +goblet +go-kart +golf ball +golfcart +gondola +gong +gown +grand piano +greenhouse +grille +grocery store +guillotine +hair slide +hair spray +half track +hammer +hamper +hand blower +hand-held computer +handkerchief +hard disc +harmonica +harp +harvester +hatchet +holster +home theater +honeycomb +hook +hoopskirt +horizontal bar +horse cart +hourglass +iPod +iron +jack-o'-lantern +jean +jeep +jersey +jigsaw puzzle +jinrikisha +joystick +kimono +knee pad +knot +lab coat +ladle +lampshade +laptop +lawn mower +lens cap +letter opener +library +lifeboat +lighter +limousine +liner +lipstick +Loafer +lotion +loudspeaker +loupe +lumbermill +magnetic compass +mailbag +mailbox +maillot +maillot +manhole cover +maraca +marimba +mask +matchstick +maypole +maze +measuring cup +medicine chest +megalith +microphone +microwave +military uniform +milk can +minibus +miniskirt +minivan +missile +mitten +mixing bowl +mobile home +Model T +modem +monastery +monitor +moped +mortar +mortarboard +mosque +mosquito net +motor scooter +mountain bike +mountain tent +mouse +mousetrap +moving van +muzzle +nail +neck brace +necklace +nipple +notebook +obelisk +oboe +ocarina +odometer +oil filter +organ +oscilloscope +overskirt +oxcart +oxygen mask +packet +paddle +paddlewheel +padlock +paintbrush +pajama +palace +panpipe +paper towel +parachute +parallel bars +park bench +parking meter +passenger car +patio +pay-phone +pedestal +pencil box +pencil sharpener +perfume +Petri dish +photocopier +pick +pickelhaube +picket fence +pickup +pier +piggy bank +pill bottle +pillow +ping-pong ball +pinwheel +pirate +pitcher +plane +planetarium +plastic bag +plate rack +plow +plunger +Polaroid camera +pole +police van +poncho +pool table +pop bottle +pot +potter's wheel +power drill +prayer rug +printer +prison +projectile +projector +puck +punching bag +purse +quill +quilt +racer +racket +radiator +radio +radio telescope +rain barrel +recreational vehicle +reel +reflex camera +refrigerator +remote control +restaurant +revolver +rifle +rocking chair +rotisserie +rubber eraser +rugby ball +rule +running shoe +safe +safety pin +saltshaker +sandal +sarong +sax +scabbard +scale +school bus +schooner +scoreboard +screen +screw +screwdriver +seat belt +sewing machine +shield +shoe shop +shoji +shopping basket +shopping cart +shovel +shower cap +shower curtain +ski +ski mask +sleeping bag +slide rule +sliding door +slot +snorkel +snowmobile +snowplow +soap dispenser +soccer ball +sock +solar dish +sombrero +soup bowl +space bar +space heater +space shuttle +spatula +speedboat +spider web +spindle +sports car +spotlight +stage +steam locomotive +steel arch bridge +steel drum +stethoscope +stole +stone wall +stopwatch +stove +strainer +streetcar +stretcher +studio couch +stupa +submarine +suit +sundial +sunglass +sunglasses +sunscreen +suspension bridge +swab +sweatshirt +swimming trunks +swing +switch +syringe +table lamp +tank +tape player +teapot +teddy +television +tennis ball +thatch +theater curtain +thimble +thresher +throne +tile roof +toaster +tobacco shop +toilet seat +torch +totem pole +tow truck +toyshop +tractor +trailer truck +tray +trench coat +tricycle +trimaran +tripod +triumphal arch +trolleybus +trombone +tub +turnstile +typewriter keyboard +umbrella +unicycle +upright +vacuum +vase +vault +velvet +vending machine +vestment +viaduct +violin +volleyball +waffle iron +wall clock +wallet +wardrobe +warplane +washbasin +washer +water bottle +water jug +water tower +whiskey jug +whistle +wig +window screen +window shade +Windsor tie +wine bottle +wing +wok +wooden spoon +wool +worm fence +wreck +yawl +yurt +web site +comic book +crossword puzzle +street sign +traffic light +book jacket +menu +plate +guacamole +consomme +hot pot +trifle +ice cream +ice lolly +French loaf +bagel +pretzel +cheeseburger +hotdog +mashed potato +head cabbage +broccoli +cauliflower +zucchini +spaghetti squash +acorn squash +butternut squash +cucumber +artichoke +bell pepper +cardoon +mushroom +Granny Smith +strawberry +orange +lemon +fig +pineapple +banana +jackfruit +custard apple +pomegranate +hay +carbonara +chocolate sauce +dough +meat loaf +pizza +potpie +burrito +red wine +espresso +cup +eggnog +alp +bubble +cliff +coral reef +geyser +lakeside +promontory +sandbar +seashore +valley +volcano +ballplayer +groom +scuba diver +rapeseed +daisy +yellow lady's slipper +corn +acorn +hip +buckeye +coral fungus +agaric +gyromitra +stinkhorn +earthstar +hen-of-the-woods +bolete +ear +toilet tissue -- GitLab From 8e0848160a7d135f728dde2519a32876b8a7e3ce Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 14:52:44 -0700 Subject: [PATCH 172/906] Prepare the XLA and TF code to correctly behave once automatic device placement will be enabled by default on computation shapes > 1. PiperOrigin-RevId: 190278826 --- tensorflow/compiler/xla/service/service.cc | 35 +++++++++++++++---- tensorflow/compiler/xla/service/service.h | 6 ++++ .../contrib/tpu/python/tpu/tpu_estimator.py | 3 +- tensorflow/contrib/tpu/python/tpu/tpu_feed.py | 19 ++++++++-- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 04487a4795..4f6a82333b 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -861,6 +861,33 @@ tensorflow::Status Service::GetDeviceHandles(const GetDeviceHandlesRequest* arg, return tensorflow::Status::OK(); } +tensorflow::Status Service::ExecuteOneToN(const ExecuteRequest* arg, + ExecuteResponse* result) { + ExecuteParallelRequest parallel_arg; + *parallel_arg.add_requests() = *arg; + ExecuteParallelResponse parallel_result; + TF_RETURN_IF_ERROR(ExecuteParallel(¶llel_arg, ¶llel_result)); + // The "result device" selection is a bit hacky, but better than assuming it + // is device 0. We have b/76035356 for restructuring the client API to clean + // up the current asymmetries and support more functionalities. + for (int64 i = 0; i < parallel_result.responses_size(); ++i) { + TF_ASSIGN_OR_RETURN(const ShapedBuffer* buffer, + allocation_tracker_.ResolveForReplica( + parallel_result.responses(i).output(), 0)); + const Shape& shape = buffer->on_host_shape(); + if (!ShapeUtil::IsEmptyTuple(shape)) { + *result = parallel_result.responses(i); + VLOG(3) << "Fetching result from device " << i << ": " + << ShapeUtil::HumanString(shape); + return Status::OK(); + } + } + TF_RET_CHECK(parallel_result.responses_size() > 0); + *result = parallel_result.responses(0); + VLOG(1) << "Defaulting to device 0 result"; + return Status::OK(); +} + tensorflow::Status Service::Execute(const ExecuteRequest* arg, ExecuteResponse* result) { VLOG(1) << "running execute request: " << arg->ShortDebugString(); @@ -877,13 +904,7 @@ tensorflow::Status Service::Execute(const ExecuteRequest* arg, // If we received multiple device handles, we must partition the module. if (arg->execution_options().device_handles_size() > 1) { - ExecuteParallelRequest parallel_arg; - *parallel_arg.add_requests() = *arg; - ExecuteParallelResponse parallel_result; - TF_RETURN_IF_ERROR(ExecuteParallel(¶llel_arg, ¶llel_result)); - TF_RET_CHECK(parallel_result.responses_size() > 0); - *result = parallel_result.responses(0); - return Status::OK(); + return ExecuteOneToN(arg, result); } TF_ASSIGN_OR_RETURN( diff --git a/tensorflow/compiler/xla/service/service.h b/tensorflow/compiler/xla/service/service.h index a76bdd89c7..3b79920b0a 100644 --- a/tensorflow/compiler/xla/service/service.h +++ b/tensorflow/compiler/xla/service/service.h @@ -346,6 +346,12 @@ class Service : public ServiceInterface { const std::function(UserComputation*)>& adder); + // Executes a single computation which has more than one target device. + // The N devices are expected to all return an empty tuple, but one, which + // will be the result of this computation. + tensorflow::Status ExecuteOneToN(const ExecuteRequest* arg, + ExecuteResponse* result); + // Convenience function which checks whether the given shape_with_layout // (presumably passed by the client to set the result layout) is valid for the // given computation result shape. diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index aaa6f3c2c1..152f8c8c69 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -931,8 +931,7 @@ class _InputPipeline(object): # In the model-parallel case, both the host-side and device-side # computations must agree on the core on which infeed takes place. We # choose to perform infeed on logical core 0 of each replica. - with ops.device(tpu.core(0)): - values = self._infeed_queue.generate_dequeue_op() + values = self._infeed_queue.generate_dequeue_op(tpu_device=0) # The unflatten process uses the structure information recorded above. return self._inputs_structure_recorder.unflatten_features_and_labels( values) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py index 42ac6eb680..604e6600c8 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py @@ -23,6 +23,7 @@ from __future__ import print_function from six.moves import xrange # pylint: disable=redefined-builtin from tensorflow.contrib.tpu.python.ops import tpu_ops +from tensorflow.contrib.tpu.python.tpu import tpu from tensorflow.contrib.tpu.python.tpu import tpu_sharding from tensorflow.python.framework import dtypes @@ -368,13 +369,20 @@ class InfeedQueue(object): policy.freeze() self._validate() - def generate_dequeue_op(self): + def generate_dequeue_op(self, tpu_device=0): """Generates the device-side Op to dequeue a tuple from the queue. Implicitly freezes the queue configuration if it is not already frozen, which will raise errors if the shapes and types have not been fully specified. + Args: + tpu_device: The TPU device ordinal where the infeed instruction should be + placed. If None, no explicit placement will be performed, and it is up + to the user to call this API from within a proper TPU device scope. + The XLA code will fail if the TPU dequeue instruction is not bound to + any device. + Returns: A list of Outputs corresponding to a shard of infeed dequeued into XLA, suitable for use within a replicated block. @@ -392,8 +400,13 @@ class InfeedQueue(object): policy.get_sharded_shape(shape) for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies) ] - return tpu_ops.infeed_dequeue_tuple( - dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name) + if tpu_device is not None: + with ops.device(tpu.core(tpu_device)): + return tpu_ops.infeed_dequeue_tuple( + dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name) + else: + return tpu_ops.infeed_dequeue_tuple( + dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name) def _generate_enqueue_op(self, inputs, -- GitLab From fce07c395d7c3931bc809183031c232651eb0638 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 15:10:56 -0700 Subject: [PATCH 173/906] add EvaluateNodes to OpDedupping test. PiperOrigin-RevId: 190282163 --- tensorflow/core/grappler/optimizers/BUILD | 1 + .../core/grappler/optimizers/arithmetic_optimizer_test.cc | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index fb13084945..92f7cce502 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -287,6 +287,7 @@ tf_cuda_cc_test( "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", + "//tensorflow/core:testlib", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:utils", "//tensorflow/core/grappler/inputs:trivial_test_graph_input_yielder", diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index 3876486d80..792f675043 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/arithmetic_optimizer.h" #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/node_def.pb.h" +#include "tensorflow/core/framework/tensor_testutil.h" #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/inputs/trivial_test_graph_input_yielder.h" #include "tensorflow/core/grappler/optimizers/constant_folding.h" @@ -157,6 +158,8 @@ TEST_F(ArithmeticOptimizerTest, OpDedupping) { ArithmeticOptimizer optimizer; GraphDef output; + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); Status status = optimizer.Optimize(nullptr, item, &output); TF_EXPECT_OK(status); // Run the optimizer twice to make sure the rewrite is idempotent. @@ -172,6 +175,10 @@ TEST_F(ArithmeticOptimizerTest, OpDedupping) { EXPECT_EQ(2, new_div.input_size()); EXPECT_EQ("c1", new_div.input(0)); EXPECT_EQ("c1", new_div.input(1)); + + auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); } TEST_F(ArithmeticOptimizerTest, OpDeduppingAssertAndCheckNumerics) { -- GitLab From db51253fce5882bf766e19b97131d90f0947d0df Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Fri, 23 Mar 2018 15:12:21 -0700 Subject: [PATCH 174/906] Convert the eager SPINN example to use tf.keras.Model and object-based checkpointing. Uses a more recursive/functional tracking style which avoids numbering layers. Maybe this is too magical and we should adapt tf.keras.Sequential first? Let me know what you think. PiperOrigin-RevId: 190282346 --- .../eager/python/examples/spinn/spinn_test.py | 24 ++- third_party/examples/eager/spinn/spinn.py | 168 ++++++++++-------- 2 files changed, 108 insertions(+), 84 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py index 081b0af14f..591d99edcd 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py +++ b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py @@ -33,6 +33,7 @@ import tensorflow as tf import tensorflow.contrib.eager as tfe from tensorflow.contrib.eager.python.examples.spinn import data from third_party.examples.eager.spinn import spinn +from tensorflow.contrib.eager.proto import checkpointable_object_graph_pb2 from tensorflow.contrib.summary import summary_test_util from tensorflow.python.eager import test from tensorflow.python.framework import test_util @@ -172,7 +173,7 @@ class SpinnTest(test_util.TensorFlowTestCase): right_in.append(tf.random_normal((1, size * 2))) tracking.append(tf.random_normal((1, tracker_size * 2))) - out = reducer(left_in, right_in, tracking=tracking) + out = reducer(left_in, right_in=right_in, tracking=tracking) self.assertEqual(batch_size, len(out)) self.assertEqual(tf.float32, out[0].dtype) self.assertEqual((1, size * 2), out[0].shape) @@ -226,7 +227,7 @@ class SpinnTest(test_util.TensorFlowTestCase): self.assertEqual((batch_size, size * 2), stacks[0][0].shape) for _ in range(2): - out1, out2 = tracker(bufs, stacks) + out1, out2 = tracker(bufs, stacks=stacks) self.assertIsNone(out2) self.assertEqual(batch_size, len(out1)) self.assertEqual(tf.float32, out1[0].dtype) @@ -259,7 +260,7 @@ class SpinnTest(test_util.TensorFlowTestCase): self.assertEqual(tf.int64, transitions.dtype) self.assertEqual((num_transitions, 1), transitions.shape) - out = s(buffers, transitions, training=True) + out = s(buffers, transitions=transitions, training=True) self.assertEqual(tf.float32, out.dtype) self.assertEqual((1, embedding_dims), out.shape) @@ -285,12 +286,15 @@ class SpinnTest(test_util.TensorFlowTestCase): vocab_size) # Invoke model under non-training mode. - logits = model(prem, prem_trans, hypo, hypo_trans, training=False) + logits = model( + prem, premise_transition=prem_trans, hypothesis=hypo, + hypothesis_transition=hypo_trans, training=False) self.assertEqual(tf.float32, logits.dtype) self.assertEqual((batch_size, d_out), logits.shape) # Invoke model under training model. - logits = model(prem, prem_trans, hypo, hypo_trans, training=True) + logits = model(prem, premise_transition=prem_trans, hypothesis=hypo, + hypothesis_transition=hypo_trans, training=True) self.assertEqual(tf.float32, logits.dtype) self.assertEqual((batch_size, d_out), logits.shape) @@ -421,8 +425,14 @@ class SpinnTest(test_util.TensorFlowTestCase): # 5. Verify that checkpoints exist and contains all the expected variables. self.assertTrue(glob.glob(os.path.join(config.logdir, "ckpt*"))) - ckpt_variable_names = [ - item[0] for item in checkpoint_utils.list_variables(config.logdir)] + object_graph_string = checkpoint_utils.load_variable( + config.logdir, name="_CHECKPOINTABLE_OBJECT_GRAPH") + object_graph = checkpointable_object_graph_pb2.CheckpointableObjectGraph() + object_graph.ParseFromString(object_graph_string) + ckpt_variable_names = set() + for node in object_graph.nodes: + for attribute in node.attributes: + ckpt_variable_names.add(attribute.full_name) self.assertIn("global_step", ckpt_variable_names) for v in trainer.variables: variable_name = v.name[:v.name.index(":")] if ":" in v.name else v.name diff --git a/third_party/examples/eager/spinn/spinn.py b/third_party/examples/eager/spinn/spinn.py index 8a1c7db2ea..f8fb6ecb0c 100644 --- a/third_party/examples/eager/spinn/spinn.py +++ b/third_party/examples/eager/spinn/spinn.py @@ -51,6 +51,9 @@ import tensorflow.contrib.eager as tfe from tensorflow.contrib.eager.python.examples.spinn import data +layers = tf.keras.layers + + def _bundle(lstm_iter): """Concatenate a list of Tensors along 1st axis and split result into two. @@ -78,17 +81,16 @@ def _unbundle(state): return tf.split(tf.concat(state, 1), state[0].shape[0], axis=0) -class Reducer(tfe.Network): +# pylint: disable=not-callable +class Reducer(tf.keras.Model): """A module that applies reduce operation on left and right vectors.""" def __init__(self, size, tracker_size=None): super(Reducer, self).__init__() - self.left = self.track_layer(tf.layers.Dense(5 * size, activation=None)) - self.right = self.track_layer( - tf.layers.Dense(5 * size, activation=None, use_bias=False)) + self.left = layers.Dense(5 * size, activation=None) + self.right = layers.Dense(5 * size, activation=None, use_bias=False) if tracker_size is not None: - self.track = self.track_layer( - tf.layers.Dense(5 * size, activation=None, use_bias=False)) + self.track = layers.Dense(5 * size, activation=None, use_bias=False) else: self.track = None @@ -123,7 +125,7 @@ class Reducer(tfe.Network): return h, c -class Tracker(tfe.Network): +class Tracker(tf.keras.Model): """A module that tracks the history of the sentence with an LSTM.""" def __init__(self, tracker_size, predict): @@ -134,10 +136,10 @@ class Tracker(tfe.Network): predict: (`bool`) Whether prediction mode is enabled. """ super(Tracker, self).__init__() - self._rnn = self.track_layer(tf.nn.rnn_cell.LSTMCell(tracker_size)) + self._rnn = tf.nn.rnn_cell.LSTMCell(tracker_size) self._state_size = tracker_size if predict: - self._transition = self.track_layer(tf.layers.Dense(4)) + self._transition = layers.Dense(4) else: self._transition = None @@ -182,7 +184,7 @@ class Tracker(tfe.Network): return unbundled, None -class SPINN(tfe.Network): +class SPINN(tf.keras.Model): """Stack-augmented Parser-Interpreter Neural Network. See https://arxiv.org/abs/1603.06021 for more details. @@ -204,9 +206,9 @@ class SPINN(tfe.Network): """ super(SPINN, self).__init__() self.config = config - self.reducer = self.track_layer(Reducer(config.d_hidden, config.d_tracker)) + self.reducer = Reducer(config.d_hidden, config.d_tracker) if config.d_tracker is not None: - self.tracker = self.track_layer(Tracker(config.d_tracker, config.predict)) + self.tracker = Tracker(config.d_tracker, config.predict) else: self.tracker = None @@ -248,7 +250,7 @@ class SPINN(tfe.Network): trans = transitions[i] if self.tracker: # Invoke tracker to obtain the current tracker states for the sentences. - tracker_states, trans_hypothesis = self.tracker(buffers, stacks) + tracker_states, trans_hypothesis = self.tracker(buffers, stacks=stacks) if trans_hypothesis: trans = tf.argmax(trans_hypothesis, axis=-1) else: @@ -264,7 +266,8 @@ class SPINN(tfe.Network): trackings.append(tracking) if rights: - reducer_output = self.reducer(lefts, rights, trackings) + reducer_output = self.reducer( + lefts, right_in=rights, tracking=trackings) reduced = iter(reducer_output) for transition, stack in zip(trans, stacks): @@ -273,7 +276,27 @@ class SPINN(tfe.Network): return _bundle([stack.pop() for stack in stacks])[0] -class SNLIClassifier(tfe.Network): +class Perceptron(tf.keras.Model): + """One layer of the SNLIClassifier multi-layer perceptron.""" + + def __init__(self, dimension, dropout_rate, previous_layer): + """Configure the Perceptron.""" + super(Perceptron, self).__init__() + self.dense = tf.keras.layers.Dense(dimension, activation=tf.nn.elu) + self.batchnorm = layers.BatchNormalization() + self.dropout = layers.Dropout(rate=dropout_rate) + self.previous_layer = previous_layer + + def call(self, x, training): + """Run previous Perceptron layers, then this one.""" + x = self.previous_layer(x, training=training) + x = self.dense(x) + x = self.batchnorm(x, training=training) + x = self.dropout(x, training=training) + return x + + +class SNLIClassifier(tf.keras.Model): """SNLI Classifier Model. A model aimed at solving the SNLI (Standford Natural Language Inference) @@ -304,29 +327,24 @@ class SNLIClassifier(tfe.Network): self.config = config self.embed = tf.constant(embed) - self.projection = self.track_layer(tf.layers.Dense(config.d_proj)) - self.embed_bn = self.track_layer(tf.layers.BatchNormalization()) - self.embed_dropout = self.track_layer( - tf.layers.Dropout(rate=config.embed_dropout)) - self.encoder = self.track_layer(SPINN(config)) - - self.feature_bn = self.track_layer(tf.layers.BatchNormalization()) - self.feature_dropout = self.track_layer( - tf.layers.Dropout(rate=config.mlp_dropout)) - - self.mlp_dense = [] - self.mlp_bn = [] - self.mlp_dropout = [] - for _ in xrange(config.n_mlp_layers): - self.mlp_dense.append(self.track_layer(tf.layers.Dense(config.d_mlp))) - self.mlp_bn.append( - self.track_layer(tf.layers.BatchNormalization())) - self.mlp_dropout.append( - self.track_layer(tf.layers.Dropout(rate=config.mlp_dropout))) - self.mlp_output = self.track_layer(tf.layers.Dense( + self.projection = layers.Dense(config.d_proj) + self.embed_bn = layers.BatchNormalization() + self.embed_dropout = layers.Dropout(rate=config.embed_dropout) + self.encoder = SPINN(config) + + self.feature_bn = layers.BatchNormalization() + self.feature_dropout = layers.Dropout(rate=config.mlp_dropout) + + current_mlp = lambda result, training: result + for _ in range(config.n_mlp_layers): + current_mlp = Perceptron(dimension=config.d_mlp, + dropout_rate=config.mlp_dropout, + previous_layer=current_mlp) + self.mlp = current_mlp + self.mlp_output = layers.Dense( config.d_out, kernel_initializer=tf.random_uniform_initializer(minval=-5e-3, - maxval=5e-3))) + maxval=5e-3)) def call(self, premise, @@ -370,10 +388,10 @@ class SNLIClassifier(tfe.Network): # Run the batch-normalized and dropout-processed word vectors through the # SPINN encoder. - premise = self.encoder(premise_embed, premise_transition, - training=training) - hypothesis = self.encoder(hypothesis_embed, hypothesis_transition, - training=training) + premise = self.encoder( + premise_embed, transitions=premise_transition, training=training) + hypothesis = self.encoder( + hypothesis_embed, transitions=hypothesis_transition, training=training) # Combine encoder outputs for premises and hypotheses into logits. # Then apply batch normalization and dropuout on the logits. @@ -383,15 +401,12 @@ class SNLIClassifier(tfe.Network): self.feature_bn(logits, training=training), training=training) # Apply the multi-layer perceptron on the logits. - for dense, bn, dropout in zip( - self.mlp_dense, self.mlp_bn, self.mlp_dropout): - logits = tf.nn.elu(dense(logits)) - logits = dropout(bn(logits, training=training), training=training) + logits = self.mlp(logits, training=training) logits = self.mlp_output(logits) return logits -class SNLIClassifierTrainer(object): +class SNLIClassifierTrainer(tfe.Checkpointable): """A class that coordinates the training of an SNLIClassifier.""" def __init__(self, snli_classifier, lr): @@ -450,10 +465,11 @@ class SNLIClassifierTrainer(object): """ with tfe.GradientTape() as tape: tape.watch(self._model.variables) + # TODO(allenl): Allow passing Layer inputs as position arguments. logits = self._model(premise, - premise_transition, - hypothesis, - hypothesis_transition, + premise_transition=premise_transition, + hypothesis=hypothesis, + hypothesis_transition=hypothesis_transition, training=True) loss = self.loss(labels, logits) gradients = tape.gradient(loss, self._model.variables) @@ -517,7 +533,9 @@ def _evaluate_on_dataset(snli_data, batch_size, trainer, use_gpu): snli_data, batch_size): if use_gpu: label, prem, hypo = label.gpu(), prem.gpu(), hypo.gpu() - logits = trainer.model(prem, prem_trans, hypo, hypo_trans, training=False) + logits = trainer.model( + prem, premise_transition=prem_trans, hypothesis=hypo, + hypothesis_transition=hypo_trans, training=False) loss_val = trainer.loss(label, logits) batch_size = tf.shape(label)[0] mean_loss(loss_val, weights=batch_size.gpu() if use_gpu else batch_size) @@ -609,29 +627,30 @@ def train_or_infer_spinn(embed, with tf.device(device), \ summary_writer.as_default(), \ tf.contrib.summary.always_record_summaries(): - with tfe.restore_variables_on_create( - tf.train.latest_checkpoint(config.logdir)): - model = SNLIClassifier(config, embed) - global_step = tf.train.get_or_create_global_step() - trainer = SNLIClassifierTrainer(model, config.lr) + model = SNLIClassifier(config, embed) + global_step = tf.train.get_or_create_global_step() + trainer = SNLIClassifierTrainer(model, config.lr) + checkpoint = tfe.Checkpoint(trainer=trainer, global_step=global_step) + checkpoint.restore(tf.train.latest_checkpoint(config.logdir)) if inference_sentence_pair: # Inference mode. - with tfe.restore_variables_on_create( - tf.train.latest_checkpoint(config.logdir)): - prem, prem_trans = inference_sentence_pair[0] - hypo, hypo_trans = inference_sentence_pair[1] - hypo_trans = inference_sentence_pair[1][1] - inference_logits = model( # pylint: disable=not-callable - tf.constant(prem), tf.constant(prem_trans), - tf.constant(hypo), tf.constant(hypo_trans), training=False) - inference_logits = inference_logits[0][1:] - max_index = tf.argmax(inference_logits) - print("\nInference logits:") - for i, (label, logit) in enumerate( - zip(data.POSSIBLE_LABELS, inference_logits)): - winner_tag = " (winner)" if max_index == i else "" - print(" {0:<16}{1:.6f}{2}".format(label + ":", logit, winner_tag)) + prem, prem_trans = inference_sentence_pair[0] + hypo, hypo_trans = inference_sentence_pair[1] + hypo_trans = inference_sentence_pair[1][1] + inference_logits = model( + tf.constant(prem), + premise_transition=tf.constant(prem_trans), + hypothesis=tf.constant(hypo), + hypothesis_transition=tf.constant(hypo_trans), + training=False) + inference_logits = inference_logits[0][1:] + max_index = tf.argmax(inference_logits) + print("\nInference logits:") + for i, (label, logit) in enumerate( + zip(data.POSSIBLE_LABELS, inference_logits)): + winner_tag = " (winner)" if max_index == i else "" + print(" {0:<16}{1:.6f}{2}".format(label + ":", logit, winner_tag)) return inference_logits train_len = train_data.num_batches(config.batch_size) @@ -650,20 +669,15 @@ def train_or_infer_spinn(embed, # remain on CPU. Same in _evaluate_on_dataset(). iterations += 1 - with tfe.restore_variables_on_create( - tf.train.latest_checkpoint(config.logdir)): - batch_train_loss, batch_train_logits = trainer.train_batch( - label, prem, prem_trans, hypo, hypo_trans) + batch_train_loss, batch_train_logits = trainer.train_batch( + label, prem, prem_trans, hypo, hypo_trans) batch_size = tf.shape(label)[0] mean_loss(batch_train_loss.numpy(), weights=batch_size.gpu() if use_gpu else batch_size) accuracy(tf.argmax(batch_train_logits, axis=1), label) if iterations % config.save_every == 0: - all_variables = trainer.variables + [global_step] - saver = tfe.Saver(all_variables) - saver.save(os.path.join(config.logdir, "ckpt"), - global_step=global_step) + checkpoint.save(os.path.join(config.logdir, "ckpt")) if iterations % config.dev_every == 0: dev_loss, dev_frac_correct = _evaluate_on_dataset( -- GitLab From a41a2975c4b39ca6026deb46f0343317da165ea6 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 15:12:35 -0700 Subject: [PATCH 175/906] [TF:XLA] Fix PotentiallyImplementedAsEigenConvolution to use the correct shape as the kernel shape A small bug is found in accessing the kernel's shape of the convolution instruction in PotentiallyImplementedAsEigenConvolution. The bug was fixed and a new testcase is created to reveal the bug. PiperOrigin-RevId: 190282385 --- tensorflow/compiler/xla/service/cpu/BUILD | 16 +++++++ .../xla/service/cpu/ir_emission_utils.cc | 8 ++-- .../xla/service/cpu/ir_emission_utils_test.cc | 46 +++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tensorflow/compiler/xla/service/cpu/ir_emission_utils_test.cc diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 093db020c0..0faa9e9c41 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -670,6 +670,22 @@ cc_library( ], ) +tf_cc_test( + name = "ir_emission_utils_test", + srcs = ["ir_emission_utils_test.cc"], + deps = [ + ":ir_emission_utils", + "//tensorflow/compiler/xla:test", + "//tensorflow/compiler/xla:test_helpers", + "//tensorflow/compiler/xla:util", + "//tensorflow/compiler/xla/service:hlo", + "//tensorflow/compiler/xla/service:hlo_matchers", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/compiler/xla/tests:xla_internal_test_main", + "//tensorflow/compiler/xla/tools/parser:hlo_parser", + ], +) + cc_library( name = "cpu_layout_assignment", srcs = ["cpu_layout_assignment.cc"], diff --git a/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc b/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc index 788217aab6..f209a69e3c 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emission_utils.cc @@ -34,14 +34,16 @@ bool PotentiallyImplementedAsEigenConvolution( // // To be sufficient, certain layout constraints need to be satisfied as well. const Shape& input_shape = convolution.operand(0)->shape(); - const Shape& kernel_shape = convolution.operand(0)->shape(); + const Shape& kernel_shape = convolution.operand(1)->shape(); if (ShapeUtil::HasZeroElements(input_shape) || ShapeUtil::HasZeroElements(kernel_shape)) { return false; } + // Make sure input and kernel has the same data type. + CHECK( + ShapeUtil::SameElementTypeIgnoringFpPrecision(input_shape, kernel_shape)); // TODO(b/65408531): Explore using Eigen dot for complex64 type. - if (ShapeUtil::ElementIsComplex(input_shape) || - ShapeUtil::ElementIsComplex(kernel_shape)) { + if (ShapeUtil::ElementIsComplex(input_shape)) { return false; } if (window_util::HasWindowReversal(convolution.window())) { diff --git a/tensorflow/compiler/xla/service/cpu/ir_emission_utils_test.cc b/tensorflow/compiler/xla/service/cpu/ir_emission_utils_test.cc new file mode 100644 index 0000000000..215f48c4cc --- /dev/null +++ b/tensorflow/compiler/xla/service/cpu/ir_emission_utils_test.cc @@ -0,0 +1,46 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/compiler/xla/service/cpu/ir_emission_utils.h" + +#include "tensorflow/compiler/xla/test.h" +#include "tensorflow/compiler/xla/tools/parser/hlo_parser.h" + +namespace xla { +namespace { + +TEST(IrEmitterTest, ConvWithZeroSizedKernelNotImplementedAsEigen) { + const char* const hlo_string = R"( +HloModule ModuleWithConv + +ENTRY Conv { + input = f32[32,50,28,28]{3,2,1,0} parameter(0) + kernel = f32[0,32,5,5]{3,2,1,0} parameter(1) + ROOT convolution = f32[64,50,24,24]{3,2,1,0} convolution(input, kernel), + window={size=5x5}, + dim_labels=b01f_01io->b01f +} +)"; + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, + tools::Parse(hlo_string)); + + HloComputation* entry_computation = module->entry_computation(); + + HloInstruction* conv_instr = entry_computation->root_instruction(); + EXPECT_FALSE(cpu::PotentiallyImplementedAsEigenConvolution(*conv_instr)); +} + +} // namespace +} // namespace xla -- GitLab From bc1dfdf8bc9e3edb4362314a89a23bb2c827bdaa Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 15:33:33 -0700 Subject: [PATCH 176/906] Adding support for analyzing assignment info for nested tuples. PiperOrigin-RevId: 190285584 --- .../py2tf/pyct/static_analysis/type_info.py | 16 ++++++++++++---- .../pyct/static_analysis/type_info_test.py | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py b/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py index 5556a58c02..a969adbeca 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py +++ b/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py @@ -168,6 +168,15 @@ class TypeInfoResolver(transformer.Base): anno.getanno(definition, 'element_type')) return node + def _process_tuple_assignment(self, source, t): + for i, e in enumerate(t.elts): + if isinstance(e, gast.Tuple): + self._process_tuple_assignment(source, e) + else: + self.scope.setval( + anno.getanno(e, anno.Basic.QN), + gast.Subscript(source, gast.Index(i), ctx=gast.Store())) + def _process_variable_assignment(self, source, targets): if isinstance(source, gast.Call): func = source.func @@ -183,10 +192,9 @@ class TypeInfoResolver(transformer.Base): for t in targets: if isinstance(t, gast.Tuple): - for i, e in enumerate(t.elts): - self.scope.setval( - anno.getanno(e, anno.Basic.QN), - gast.Subscript(source, gast.Index(i), ctx=gast.Store())) + # need to recurse on the case of assigning nested tuples, + # ex. a, (b, c) = f() + self._process_tuple_assignment(source, t) elif isinstance(t, (gast.Name, gast.Attribute)): self.scope.setval(anno.getanno(t, anno.Basic.QN), source) else: diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py b/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py index 0d9d5a85f0..8a8956197d 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py +++ b/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py @@ -196,6 +196,23 @@ class TypeInfoResolverTest(test.TestCase): f_ref = node.body[0].body[1].value self.assertEqual(anno.getanno(f_ref, 'element_type'), Foo) + def test_nested_assignment(self): + + def test_fn(foo): + a, (b, c) = foo + return a, b, c + + node = self._parse_and_analyze(test_fn, {'foo': (1, 2, 3)}) + lhs = node.body[0].body[1].value.elts + a = lhs[0] + b = lhs[1] + c = lhs[2] + # TODO(mdan): change these once we have the live values propagating + # correctly + self.assertFalse(anno.hasanno(a, 'live_val')) + self.assertFalse(anno.hasanno(b, 'live_val')) + self.assertFalse(anno.hasanno(c, 'live_val')) + if __name__ == '__main__': test.main() -- GitLab From 7c57af0c860746e8a91b13bade87bdd1af9dc9e1 Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Fri, 23 Mar 2018 15:34:47 -0700 Subject: [PATCH 177/906] [XLA] Don't CSE instructions which have side-effects PiperOrigin-RevId: 190285774 --- tensorflow/compiler/xla/service/hlo_cse.cc | 7 ++++++- tensorflow/compiler/xla/service/hlo_cse_test.cc | 13 +++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tensorflow/compiler/xla/service/hlo_cse.cc b/tensorflow/compiler/xla/service/hlo_cse.cc index 279edd4ba8..cd7cbbdd71 100644 --- a/tensorflow/compiler/xla/service/hlo_cse.cc +++ b/tensorflow/compiler/xla/service/hlo_cse.cc @@ -109,6 +109,11 @@ StatusOr HloCSE::Run(HloModule* module) { continue; } + // Skip instructions which have side effects. + if (instruction->HasSideEffect()) { + continue; + } + // An instruction is considered to be equivalent to another only if they // share the exact same set of operands. So to find equivalent // instructions, we just search among instructions which share operand(0) @@ -118,7 +123,7 @@ StatusOr HloCSE::Run(HloModule* module) { tensorflow::gtl::InlinedVector equivalent_instructions; for (HloInstruction* user : operand->users()) { - if (user != instruction && + if (user != instruction && !user->HasSideEffect() && user->Identical(*instruction, eq_instructions, eq_computations, is_layout_sensitive_)) { equivalent_instructions.push_back(user); diff --git a/tensorflow/compiler/xla/service/hlo_cse_test.cc b/tensorflow/compiler/xla/service/hlo_cse_test.cc index 3601a790c4..df8853f34f 100644 --- a/tensorflow/compiler/xla/service/hlo_cse_test.cc +++ b/tensorflow/compiler/xla/service/hlo_cse_test.cc @@ -414,8 +414,7 @@ TEST_F(HloCseTest, DoNotCombineRng) { EXPECT_THAT(root, op::Add(rng1, rng2)); } -// TODO(b/28245743): Handle impure functions correctly in CSE. -TEST_F(HloCseTest, DISABLED_DoNotCombineCallsToImpureFunctions) { +TEST_F(HloCseTest, DoNotCombineCallsToImpureFunctions) { // Test that two calls to an impure function are not commoned. RNG // is the source of the impurity. @@ -458,14 +457,16 @@ TEST_F(HloCseTest, DISABLED_DoNotCombineCallsToImpureFunctions) { HloInstruction* root = computation->root_instruction(); EXPECT_THAT(root, op::Add(op::Map(), op::Map())); + VLOG(3) << "before: " << module->ToString(); + HloCSE cse(/*is_layout_sensitive=*/false); - EXPECT_TRUE(cse.Run(module.get()).ValueOrDie()); + EXPECT_FALSE(cse.Run(module.get()).ValueOrDie()); + + VLOG(3) << "after: " << module->ToString(); EXPECT_EQ(4, computation->instruction_count()); root = computation->root_instruction(); - auto operand = root->operand(0)->operand(0); - EXPECT_THAT(operand, op::Map()); - EXPECT_THAT(root, op::Add(operand, operand)); + EXPECT_THAT(root, op::Add(op::Map(op::Constant()), op::Map(op::Constant()))); } } // namespace -- GitLab From 95a87277174f9fc49b4b5d9c1edbbd149bd0274c Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Fri, 23 Mar 2018 15:52:35 -0700 Subject: [PATCH 178/906] [XLA:CPU] Update calls to IRBuilder::CreateMemCpy to the 2-alignment form. The single alignment version is going away. PiperOrigin-RevId: 190288581 --- .../compiler/xla/service/cpu/ir_emitter.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index 3b8056d505..3405277d44 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -438,12 +438,14 @@ Status IrEmitter::EmitXfeedTransfer(XfeedKind kind, const Shape& shape, if (kind == XfeedKind::kInfeed) { // Copy to the program buffer address from the acquired buffer. - ir_builder_.CreateMemCpy(program_buffer_address, acquired_pointer, - length_32, 1); + ir_builder_.CreateMemCpy(program_buffer_address, /*DstAlign=*/1, + acquired_pointer, + /*SrcAlign=*/1, length_32); } else { // Outfeed -- copy from the in-program address to the acquired buffer. - ir_builder_.CreateMemCpy(acquired_pointer, program_buffer_address, - length_32, 1); + ir_builder_.CreateMemCpy(acquired_pointer, /*DstAlign=*/1, + program_buffer_address, + /*SrcAlign=*/1, length_32); } ir_builder_.CreateCall(release_func, @@ -2441,7 +2443,8 @@ void IrEmitter::EmitTransferElements(llvm::Value* target, llvm::Value* source, target_array.AnnotateLoadStoreInstructionWithMetadata(store_instruction); } else { auto* memcpy_instruction = ir_builder_.CreateMemCpy( - target, source, element_count * primitive_type_size, element_alignment); + target, /*DstAlign=*/element_alignment, source, + /*SrcAlign=*/element_alignment, element_count * primitive_type_size); // The memcpy does the load and the store internally. The aliasing related // metadata has to reflect that. @@ -2905,7 +2908,8 @@ Status IrEmitter::EmitMemcpy(const HloInstruction& source, llvm::Value* destination_value = GetEmittedValueFor(&destination); int64 source_size = ByteSizeOf(source.shape()); // TODO(b/63762267): Be more aggressive about specifying alignment. - ir_builder_.CreateMemCpy(destination_value, source_value, source_size, 1); + ir_builder_.CreateMemCpy(destination_value, /*DstAlign=*/1, source_value, + /*SrcAlign=*/1, source_size); return Status::OK(); } -- GitLab From 084c10784887d7c4d467416430626cf7eb333cb8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 16:00:14 -0700 Subject: [PATCH 179/906] Extended scatter operations to work with a scalar update parameter and added scatter-min and scatter-max operations. PiperOrigin-RevId: 190289664 --- .../base_api/api_def_ResourceScatterAdd.pbtxt | 2 +- .../base_api/api_def_ResourceScatterDiv.pbtxt | 43 ++++ .../base_api/api_def_ResourceScatterMax.pbtxt | 43 ++++ .../base_api/api_def_ResourceScatterMin.pbtxt | 43 ++++ .../base_api/api_def_ResourceScatterMul.pbtxt | 43 ++++ .../base_api/api_def_ResourceScatterSub.pbtxt | 43 ++++ .../api_def/base_api/api_def_ScatterAdd.pbtxt | 2 +- .../api_def/base_api/api_def_ScatterDiv.pbtxt | 2 +- .../api_def/base_api/api_def_ScatterMax.pbtxt | 60 +++++ .../api_def/base_api/api_def_ScatterMin.pbtxt | 60 +++++ .../api_def/base_api/api_def_ScatterMul.pbtxt | 2 +- .../api_def/base_api/api_def_ScatterSub.pbtxt | 2 +- .../base_api/api_def_ScatterUpdate.pbtxt | 2 +- .../api_def_ResourceScatterDiv.pbtxt | 4 + .../api_def_ResourceScatterMax.pbtxt | 4 + .../api_def_ResourceScatterMin.pbtxt | 4 + .../api_def_ResourceScatterMul.pbtxt | 4 + .../api_def_ResourceScatterSub.pbtxt | 4 + .../core/kernels/resource_variable_ops.cc | 81 ++++--- tensorflow/core/kernels/scatter_functor.cc | 27 ++- tensorflow/core/kernels/scatter_functor.h | 170 +++++++++++++- .../core/kernels/scatter_functor_gpu.cu.cc | 9 +- .../core/kernels/scatter_functor_gpu.cu.h | 108 +++++++-- tensorflow/core/kernels/scatter_op.cc | 126 ++++++---- tensorflow/core/kernels/scatter_op_gpu.cu.cc | 9 +- tensorflow/core/kernels/scatter_op_test.cc | 26 ++- tensorflow/core/ops/resource_variable_ops.cc | 92 +++++--- tensorflow/core/ops/state_ops.cc | 25 +- .../docs_src/api_guides/python/state_ops.md | 2 + .../resource_variable_ops_test.py | 215 ++++++++++++++++++ .../python/kernel_tests/scatter_ops_test.py | 145 +++++++++++- tensorflow/python/ops/standard_ops.py | 2 + tensorflow/python/ops/state_ops.py | 2 + tensorflow/tools/api/golden/tensorflow.pbtxt | 8 + 34 files changed, 1261 insertions(+), 153 deletions(-) create mode 100644 tensorflow/core/api_def/base_api/api_def_ResourceScatterDiv.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ResourceScatterMax.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ResourceScatterMin.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ResourceScatterMul.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ResourceScatterSub.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ScatterMax.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_ScatterMin.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ResourceScatterDiv.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ResourceScatterMax.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ResourceScatterMin.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ResourceScatterMul.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ResourceScatterSub.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterAdd.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterAdd.pbtxt index 9e0de08267..4eb6eb4e4d 100644 --- a/tensorflow/core/api_def/base_api/api_def_ResourceScatterAdd.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterAdd.pbtxt @@ -34,7 +34,7 @@ This operation computes Duplicate entries are handled correctly: if multiple `indices` reference the same location, their contributions add. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`.
diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterDiv.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterDiv.pbtxt new file mode 100644 index 0000000000..47148f7b03 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterDiv.pbtxt @@ -0,0 +1,43 @@ +op { + graph_op_name: "ResourceScatterDiv" + in_arg { + name: "resource" + description: < + +
+END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterMax.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMax.pbtxt new file mode 100644 index 0000000000..71f06d9a43 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMax.pbtxt @@ -0,0 +1,43 @@ +op { + graph_op_name: "ResourceScatterMax" + in_arg { + name: "resource" + description: < + + +END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterMin.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMin.pbtxt new file mode 100644 index 0000000000..08e40ee2a8 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMin.pbtxt @@ -0,0 +1,43 @@ +op { + graph_op_name: "ResourceScatterMin" + in_arg { + name: "resource" + description: < + + +END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterMul.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMul.pbtxt new file mode 100644 index 0000000000..5c63549d81 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterMul.pbtxt @@ -0,0 +1,43 @@ +op { + graph_op_name: "ResourceScatterMul" + in_arg { + name: "resource" + description: < + + +END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ResourceScatterSub.pbtxt b/tensorflow/core/api_def/base_api/api_def_ResourceScatterSub.pbtxt new file mode 100644 index 0000000000..e71e60cbee --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ResourceScatterSub.pbtxt @@ -0,0 +1,43 @@ +op { + graph_op_name: "ResourceScatterSub" + in_arg { + name: "resource" + description: < + + +END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterAdd.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterAdd.pbtxt index 4b5201f025..9da9d09ea6 100644 --- a/tensorflow/core/api_def/base_api/api_def_ScatterAdd.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ScatterAdd.pbtxt @@ -51,7 +51,7 @@ This makes it easier to chain operations that need to use the reset value. Duplicate entries are handled correctly: if multiple `indices` reference the same location, their contributions add. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`.
diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterDiv.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterDiv.pbtxt index 771cf0b591..8e99718c7e 100644 --- a/tensorflow/core/api_def/base_api/api_def_ScatterDiv.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ScatterDiv.pbtxt @@ -53,6 +53,6 @@ This makes it easier to chain operations that need to use the reset value. Duplicate entries are handled correctly: if multiple `indices` reference the same location, their contributions divide. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`. END } diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterMax.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterMax.pbtxt new file mode 100644 index 0000000000..7b52dad4a1 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ScatterMax.pbtxt @@ -0,0 +1,60 @@ +op { + graph_op_name: "ScatterMax" + in_arg { + name: "ref" + description: < + +
+END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterMin.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterMin.pbtxt new file mode 100644 index 0000000000..721ac0ff35 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ScatterMin.pbtxt @@ -0,0 +1,60 @@ +op { + graph_op_name: "ScatterMin" + in_arg { + name: "ref" + description: < + + +END +} diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterMul.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterMul.pbtxt index a51f571b00..b9e293ba9e 100644 --- a/tensorflow/core/api_def/base_api/api_def_ScatterMul.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ScatterMul.pbtxt @@ -53,6 +53,6 @@ This makes it easier to chain operations that need to use the reset value. Duplicate entries are handled correctly: if multiple `indices` reference the same location, their contributions multiply. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`. END } diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterSub.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterSub.pbtxt index c0d3a4a133..d12b3e68c2 100644 --- a/tensorflow/core/api_def/base_api/api_def_ScatterSub.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ScatterSub.pbtxt @@ -51,7 +51,7 @@ This makes it easier to chain operations that need to use the reset value. Duplicate entries are handled correctly: if multiple `indices` reference the same location, their (negated) contributions add. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`.
diff --git a/tensorflow/core/api_def/base_api/api_def_ScatterUpdate.pbtxt b/tensorflow/core/api_def/base_api/api_def_ScatterUpdate.pbtxt index c44dbbd233..4804908afc 100644 --- a/tensorflow/core/api_def/base_api/api_def_ScatterUpdate.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ScatterUpdate.pbtxt @@ -54,7 +54,7 @@ If values in `ref` is to be updated more than once, because there are duplicate entries in `indices`, the order at which the updates happen for each value is undefined. -Requires `updates.shape = indices.shape + ref.shape[1:]`. +Requires `updates.shape = indices.shape + ref.shape[1:]` or `updates.shape = []`.
diff --git a/tensorflow/core/api_def/python_api/api_def_ResourceScatterDiv.pbtxt b/tensorflow/core/api_def/python_api/api_def_ResourceScatterDiv.pbtxt new file mode 100644 index 0000000000..56b5a46d10 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ResourceScatterDiv.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ResourceScatterDiv" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ResourceScatterMax.pbtxt b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMax.pbtxt new file mode 100644 index 0000000000..8119bcc6c6 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMax.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ResourceScatterMax" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ResourceScatterMin.pbtxt b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMin.pbtxt new file mode 100644 index 0000000000..d874aef3fe --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMin.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ResourceScatterMin" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ResourceScatterMul.pbtxt b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMul.pbtxt new file mode 100644 index 0000000000..365a37fa0d --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ResourceScatterMul.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ResourceScatterMul" + visibility: HIDDEN +} diff --git a/tensorflow/core/api_def/python_api/api_def_ResourceScatterSub.pbtxt b/tensorflow/core/api_def/python_api/api_def_ResourceScatterSub.pbtxt new file mode 100644 index 0000000000..72dc5bf889 --- /dev/null +++ b/tensorflow/core/api_def/python_api/api_def_ResourceScatterSub.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "ResourceScatterSub" + visibility: HIDDEN +} diff --git a/tensorflow/core/kernels/resource_variable_ops.cc b/tensorflow/core/kernels/resource_variable_ops.cc index aecad0185f..e134e476f6 100644 --- a/tensorflow/core/kernels/resource_variable_ops.cc +++ b/tensorflow/core/kernels/resource_variable_ops.cc @@ -619,22 +619,35 @@ class ResourceScatterUpdateOp : public OpKernel { if (N > 0) { auto indices_flat = indices.flat(); auto params_flat = params->flat_outer_dims(); - int64 num_updates = updates.NumElements(); - OP_REQUIRES(c, num_updates % N == 0, - errors::InvalidArgument( - "shape of indices (", indices.shape().DebugString(), - ") is not compatible with the shape of updates (", - updates.shape().DebugString(), ")")); - auto updates_flat = updates.shaped({N, num_updates / N}); - - functor::ScatterFunctor functor; - const Index bad_i = functor(c, c->template eigen_device(), - params_flat, updates_flat, indices_flat); - OP_REQUIRES(c, bad_i < 0, - errors::InvalidArgument( - "indices", SliceDebugString(indices.shape(), bad_i), - " = ", indices_flat(bad_i), " is not in [0, ", - params->dim_size(0), ")")); + if (TensorShapeUtils::IsScalar(updates.shape())) { + const auto update = updates.scalar(); + + functor::ScatterScalarFunctor functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, update, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params->dim_size(0), ")")); + } else { + int64 num_updates = updates.NumElements(); + OP_REQUIRES(c, num_updates % N == 0, + errors::InvalidArgument( + "shape of indices (", indices.shape().DebugString(), + ") is not compatible with the shape of updates (", + updates.shape().DebugString(), ")")); + auto updates_flat = updates.shaped({N, num_updates / N}); + + functor::ScatterFunctor functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, updates_flat, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params->dim_size(0), ")")); + } } } }; @@ -652,35 +665,51 @@ class ResourceScatterUpdateOp : public OpKernel { REGISTER_SCATTER_KERNEL_INDEX(type, int32, dev, name, op); \ REGISTER_SCATTER_KERNEL_INDEX(type, int64, dev, name, op); -// TODO(apassos) add the other types here. -#define REGISTER_SCATTER_ARITHEMTIC(type, dev) \ +#define REGISTER_SCATTER_ARITHMETIC(type, dev) \ REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterAdd", \ scatter_op::UpdateOp::ADD); \ + REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterSub", \ + scatter_op::UpdateOp::SUB); \ + REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterMul", \ + scatter_op::UpdateOp::MUL); \ + REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterDiv", \ + scatter_op::UpdateOp::DIV); \ REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterUpdate", \ scatter_op::UpdateOp::ASSIGN); +#define REGISTER_SCATTER_MINMAX(type, dev) \ + REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterMin", \ + scatter_op::UpdateOp::MIN); \ + REGISTER_SCATTER_KERNEL(type, dev, "ResourceScatterMax", \ + scatter_op::UpdateOp::MAX); // Registers CPU kernels. -#define REGISTER_SCATTER_ARITHEMTIC_CPU(type) \ - REGISTER_SCATTER_ARITHEMTIC(type, CPU); +#define REGISTER_SCATTER_ARITHMETIC_CPU(type) \ + REGISTER_SCATTER_ARITHMETIC(type, CPU); +#define REGISTER_SCATTER_MINMAX_CPU(type) REGISTER_SCATTER_MINMAX(type, CPU); -TF_CALL_NUMBER_TYPES(REGISTER_SCATTER_ARITHEMTIC_CPU); +TF_CALL_NUMBER_TYPES(REGISTER_SCATTER_ARITHMETIC_CPU); +TF_CALL_REAL_NUMBER_TYPES(REGISTER_SCATTER_MINMAX_CPU); REGISTER_SCATTER_KERNEL(string, CPU, "ResourceScatterUpdate", scatter_op::UpdateOp::ASSIGN); // Registers GPU kernels. #if GOOGLE_CUDA -#define REGISTER_SCATTER_ARITHEMTIC_GPU(type) \ - REGISTER_SCATTER_ARITHEMTIC(type, GPU); +#define REGISTER_SCATTER_ARITHMETIC_GPU(type) \ + REGISTER_SCATTER_ARITHMETIC(type, GPU); +#define REGISTER_SCATTER_MINMAX_GPU(type) REGISTER_SCATTER_MINMAX(type, GPU); #define REGISTER_SCATTER_UPDATE_GPU(type) REGISTER_SCATTER_UPDATE(type, GPU); -TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHEMTIC_GPU); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHMETIC_GPU); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_MINMAX_GPU); #endif // GOOGLE_CUDA -#undef REGISTER_SCATTER_ARITHEMTIC -#undef REGISTER_SCATTER_ARITHEMTIC_CPU +#undef REGISTER_SCATTER_ARITHMETIC +#undef REGISTER_SCATTER_ARITHMETIC_CPU +#undef REGISTER_SCATTER_MINMAX +#undef REGISTER_SCATTER_MINMAX_CPU #undef REGISTER_SCATTER_KERNEL #undef REGISTER_SCATTER_KERNEL_INDEX diff --git a/tensorflow/core/kernels/scatter_functor.cc b/tensorflow/core/kernels/scatter_functor.cc index 7eba82899f..cf5408123f 100644 --- a/tensorflow/core/kernels/scatter_functor.cc +++ b/tensorflow/core/kernels/scatter_functor.cc @@ -26,21 +26,30 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { // Forward declarations of the functor specializations for GPU. -#define DECLARE_GPU_SPECS_OP(T, Index, op) \ - template <> \ - Index ScatterFunctor::operator()( \ - OpKernelContext* c, const GPUDevice& d, \ - typename TTypes::Matrix params, \ - typename TTypes::ConstMatrix updates, \ - typename TTypes::ConstFlat indices); \ - extern template struct ScatterFunctor; +#define DECLARE_GPU_SPECS_OP(T, Index, op) \ + template <> \ + Index ScatterFunctor::operator()( \ + OpKernelContext* c, const GPUDevice& d, \ + typename TTypes::Matrix params, \ + typename TTypes::ConstMatrix updates, \ + typename TTypes::ConstFlat indices); \ + extern template struct ScatterFunctor; \ + template <> \ + Index ScatterScalarFunctor::operator()( \ + OpKernelContext* c, const GPUDevice& d, \ + typename TTypes::Matrix params, \ + const typename TTypes::ConstScalar update, \ + typename TTypes::ConstFlat indices); \ + extern template struct ScatterScalarFunctor; #define DECLARE_GPU_SPECS_INDEX(T, Index) \ DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ASSIGN); \ DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ADD); \ DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::SUB); \ DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MUL); \ - DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); + DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); \ + DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MIN); \ + DECLARE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MAX); #define DECLARE_GPU_SPECS(T) \ DECLARE_GPU_SPECS_INDEX(T, int32); \ diff --git a/tensorflow/core/kernels/scatter_functor.h b/tensorflow/core/kernels/scatter_functor.h index 079f15e101..52666645bf 100644 --- a/tensorflow/core/kernels/scatter_functor.h +++ b/tensorflow/core/kernels/scatter_functor.h @@ -18,6 +18,8 @@ limitations under the License. #include +#include "third_party/eigen3/Eigen/Core" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/bounds_check.h" #include "tensorflow/core/platform/types.h" @@ -33,7 +35,7 @@ typedef Eigen::SyclDevice SYCLDevice; namespace scatter_op { -enum class UpdateOp { ASSIGN, ADD, SUB, MUL, DIV }; +enum class UpdateOp { ASSIGN, ADD, SUB, MUL, DIV, MIN, MAX }; namespace internal { @@ -45,6 +47,10 @@ struct Assign { static void Run(Params p, Update u) { p = u; } + template + static void RunScalar(Params p, Update u) { + p.setConstant(u); + } }; template <> struct Assign { @@ -52,6 +58,10 @@ struct Assign { static void Run(Params p, Update u) { p += u; } + template + static void RunScalar(Params p, Update u) { + p = p + u; + } }; template <> struct Assign { @@ -59,6 +69,10 @@ struct Assign { static void Run(Params p, Update u) { p -= u; } + template + static void RunScalar(Params p, Update u) { + p = p + static_cast(-u); + } }; template <> struct Assign { @@ -66,6 +80,10 @@ struct Assign { static void Run(Params p, Update u) { p *= u; } + template + static void RunScalar(Params p, Update u) { + p = p * u; + } }; template <> struct Assign { @@ -73,6 +91,34 @@ struct Assign { static void Run(Params p, Update u) { p /= u; } + template + static void RunScalar(Params p, Update u) { + p = p / u; + } +}; +template <> +struct Assign { + // This method requires that Params and Update are tensor types. + template + static void Run(Params p, Update u) { + p = p.cwiseMin(u); + } + // Same thing, but for Update being a scalar type. + template + static void RunScalar(Params p, Update u) { + p = p.cwiseMin(u); + } +}; +template <> +struct Assign { + template + static void Run(Params p, Update u) { + p = p.cwiseMax(u); + } + template + static void RunScalar(Params p, Update u) { + p = p.cwiseMax(u); + } }; #ifdef TENSORFLOW_USE_SYCL @@ -117,6 +163,22 @@ struct AssignSYCL { p.device(d) = p / u; } }; + +template <> +struct AssignSYCL { + template + static void Run(Device d, Params p, Update u) { + p.device(d) = p.cwiseMin(u); + } +}; + +template <> +struct AssignSYCL { + template + static void Run(Device d, Params p, Update u) { + p.device(d) = p.cwiseMax(u); + } +}; #endif // TENSORFLOW_USE_SYCL } // namespace internal @@ -241,6 +303,112 @@ struct ScatterFunctorSYCL { }; #endif // TENSORFLOW_USE_SYCL +template +struct ScatterScalarFunctor { + Index operator()(OpKernelContext* c, const Device& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::ConstFlat indices); +}; + +template +struct ScatterScalarFunctorBase { + Index operator()(OpKernelContext* c, const Device& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::ConstFlat indices) { + // indices and params sizes were validated in DoCompute(). + const Index N = static_cast(indices.size()); + const Index limit = static_cast(params.dimension(0)); + for (Index i = 0; i < N; i++) { + // Grab the index and check its validity. An earlier version of the + // code checked it and then grabbed it from memory a second time, which + // was a security risk since it could have changed in between. + const Index index = ::tensorflow::internal::SubtleMustCopy(indices(i)); + if (!FastBoundsCheck(index, limit)) return i; + // Broadcast update to params[index] + scatter_op::internal::Assign::RunScalar( + params.template chip<0>(index), update()); + } + return -1; + } +}; + +#ifdef TENSORFLOW_USE_SYCL +template +struct ScatterScalarFunctorBase { + Index operator()(OpKernelContext* c, const SYCLDevice& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::ConstFlat indices) { + // indices and params sizes were validated in DoCompute(). + const Index N = static_cast(indices.size()); + const Index limit = static_cast(params.dimension(0)); + for (Index i = 0; i < N; i++) { + // Grab the index and check its validity. An earlier version of the + // code checked it and then grabbed it from memory a second time, which + // was a security risk since it could have changed in between. + const Index index = ::tensorflow::internal::SubtleMustCopy(indices(i)); + if (!FastBoundsCheck(index, limit)) return i; + // Broadcast update to params[index] + scatter_op::internal::AssignSYCL::RunScalar( + d, params.template chip<0>(index), update); + } + return -1; + } +}; +#endif // TENSORFLOW_USE_SYCL + +template +struct ScatterScalarFunctorBase { + Index operator()(OpKernelContext* c, const CPUDevice& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::ConstFlat indices) { + // indices and params sizes were validated in DoCompute(). + const Index N = static_cast(indices.size()); + const Index limit = static_cast(params.dimension(0)); + for (Index i = 0; i < N; i++) { + // Grab the index and check its validity. An earlier version of the + // code checked it and then grabbed it from memory a second time, which + // was a security risk since it could have changed in between. + const Index index = ::tensorflow::internal::SubtleMustCopy(indices(i)); + if (!FastBoundsCheck(index, limit)) return i; + // Broadcast update to params[index] + scatter_op::internal::Assign::RunScalar( + params.template chip<0>(index), update()); + } + return -1; + } +}; + +template +struct ScatterScalarFunctor + : ScatterScalarFunctorBase {}; + +#ifdef TENSORFLOW_USE_SYCL +template +struct ScatterScalarFunctorSYCL { + Index operator()(OpKernelContext* c, const SYCLDevice& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::Flat indices) { + // indices and params sizes were validated in DoCompute(). + const Index N = static_cast(indices.size()); + const Index limit = static_cast(params.dimension(0)); + for (Index i = 0; i < N; i++) { + const Index index = ::tensorflow::internal::SubtleMustCopy(indices(i)); + if (!FastBoundsCheck(index, limit)) return i; + // Broadcast update to params[index] + scatter_op::internal::AssignSYCL::Run( + d, params.template chip<0>(index), update()); + } + return -1; + } +}; +#endif // TENSORFLOW_USE_SYCL + } // namespace functor } // namespace tensorflow diff --git a/tensorflow/core/kernels/scatter_functor_gpu.cu.cc b/tensorflow/core/kernels/scatter_functor_gpu.cu.cc index 52972997cc..59911bf0d2 100644 --- a/tensorflow/core/kernels/scatter_functor_gpu.cu.cc +++ b/tensorflow/core/kernels/scatter_functor_gpu.cu.cc @@ -23,15 +23,18 @@ namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; -#define DEFINE_GPU_SPECS_OP(T, Index, op) \ - template struct functor::ScatterFunctor; +#define DEFINE_GPU_SPECS_OP(T, Index, op) \ + template struct functor::ScatterFunctor; \ + template struct functor::ScatterScalarFunctor; #define DEFINE_GPU_SPECS_INDEX(T, Index) \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ASSIGN); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ADD); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::SUB); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MUL); \ - DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); \ + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MIN); \ + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MAX); #define DEFINE_GPU_SPECS(T) \ DEFINE_GPU_SPECS_INDEX(T, int32); \ diff --git a/tensorflow/core/kernels/scatter_functor_gpu.cu.h b/tensorflow/core/kernels/scatter_functor_gpu.cu.h index be18658543..70809e4dcf 100644 --- a/tensorflow/core/kernels/scatter_functor_gpu.cu.h +++ b/tensorflow/core/kernels/scatter_functor_gpu.cu.h @@ -29,12 +29,53 @@ namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; +namespace scatter_op_gpu { + +template +struct ScatterOpKernelBody; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { *dest = src; } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicAdd(dest, src); } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicSub(dest, src); } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicMul(dest, src); } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicDiv(dest, src); } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicMin(dest, src); } +}; + +template +struct ScatterOpKernelBody { + __device__ void operator()(T* dest, T src) const { CudaAtomicMax(dest, src); } +}; + template __global__ void ScatterOpCustomKernel(T* params, const T* updates, const Index* indices, Index first_dim_size, Index updates_size, Index indices_size) { Index update_block = updates_size / indices_size; + ScatterOpKernelBody body; CUDA_1D_KERNEL_LOOP(i, updates_size) { int indices_i = i / update_block; int updates_i = i; @@ -44,31 +85,33 @@ __global__ void ScatterOpCustomKernel(T* params, const T* updates, continue; } int params_i = param_first_index * update_block + (i % update_block); - switch (op) { - case scatter_op::UpdateOp::ASSIGN: { - params[params_i] = ldg(updates + updates_i); - break; - } - case scatter_op::UpdateOp::ADD: { - CudaAtomicAdd(params + params_i, ldg(updates + updates_i)); - break; - } - case scatter_op::UpdateOp::SUB: { - CudaAtomicSub(params + params_i, ldg(updates + updates_i)); - break; - } - case scatter_op::UpdateOp::MUL: { - CudaAtomicMul(params + params_i, ldg(updates + updates_i)); - break; - } - case scatter_op::UpdateOp::DIV: { - CudaAtomicDiv(params + params_i, ldg(updates + updates_i)); - break; - } + body(¶ms[params_i], ldg(updates + updates_i)); + } +} + +template +__global__ void ScatterScalarOpCustomKernel(T* params, const T* update, + const Index* indices, + Index first_dim_size, + Index indices_size, + Index synthesized_updates_size) { + Index update_block = synthesized_updates_size / indices_size; + ScatterOpKernelBody body; + CUDA_1D_KERNEL_LOOP(i, synthesized_updates_size) { + int indices_i = i / update_block; + int param_first_index = indices[indices_i]; + const T update_val = *update; + if (!(param_first_index >= 0 && param_first_index < first_dim_size)) { + // Ignore indices that are out of range. + continue; } + int params_i = param_first_index * update_block + (i % update_block); + body(¶ms[params_i], update_val); } } +} // namespace scatter_op_gpu + namespace functor { // Specialization for a GPU device. template @@ -84,7 +127,7 @@ struct ScatterFunctor { const Index indices_size = indices.size(); const Index updates_size = updates.size(); CudaLaunchConfig config = GetCudaLaunchConfig(updates_size, d); - ScatterOpCustomKernel + scatter_op_gpu::ScatterOpCustomKernel <<>>( params.data(), updates.data(), indices.data(), first_dim_size, updates_size, indices_size); @@ -92,6 +135,27 @@ struct ScatterFunctor { } }; +template +struct ScatterScalarFunctor { + Index operator()(OpKernelContext* c, const GPUDevice& d, + typename TTypes::Matrix params, + const typename TTypes::ConstScalar update, + typename TTypes::ConstFlat indices) { + // TODO(b/31801742): Implement indices range check. The hardest part is + // with returning a value after the range check, as we do not want to do + // device to host memcpy during a stream. + const Index first_dim_size = params.dimension(0); + const Index indices_size = indices.size(); + const Index synthesized_updates_size = indices_size * params.dimension(1); + CudaLaunchConfig config = GetCudaLaunchConfig(synthesized_updates_size, d); + scatter_op_gpu::ScatterScalarOpCustomKernel + <<>>( + params.data(), update.data(), indices.data(), first_dim_size, + indices_size, synthesized_updates_size); + return -1; + } +}; + } // namespace functor } // namespace tensorflow diff --git a/tensorflow/core/kernels/scatter_op.cc b/tensorflow/core/kernels/scatter_op.cc index 282165349f..0fbde764d5 100644 --- a/tensorflow/core/kernels/scatter_op.cc +++ b/tensorflow/core/kernels/scatter_op.cc @@ -38,6 +38,7 @@ typedef Eigen::SyclDevice SYCLDevice; // Check whether updates.shape = indices.shape + params.shape[1:] static bool ValidShapes(const Tensor& params, const Tensor& updates, const Tensor& indices) { + if (updates.dims() == 0) return true; if (updates.dims() != indices.dims() + params.dims() - 1) return false; for (int d = 0; d < indices.dims(); d++) { if (updates.dim_size(d) != indices.dim_size(d)) { @@ -61,11 +62,11 @@ static void DoValidationChecking(OpKernelContext* c, const Tensor& params, params.shape().DebugString())); OP_REQUIRES( c, ValidShapes(params, updates, indices), - errors::InvalidArgument( - "Must have updates.shape = indices.shape + params.shape[1:], got ", - "updates.shape ", updates.shape().DebugString(), ", indices.shape ", - indices.shape().DebugString(), ", params.shape ", - params.shape().DebugString())); + errors::InvalidArgument("Must have updates.shape = indices.shape + " + "params.shape[1:] or updates.shape = [], got ", + "updates.shape ", updates.shape().DebugString(), + ", indices.shape ", indices.shape().DebugString(), + ", params.shape ", params.shape().DebugString())); } template @@ -122,16 +123,31 @@ class ScatterUpdateOp : public OpKernel { if (N > 0) { auto indices_flat = indices.flat(); auto params_flat = params.flat_outer_dims(); - auto updates_flat = updates.shaped({N, updates.NumElements() / N}); - - functor::ScatterFunctor functor; - const Index bad_i = functor(c, c->template eigen_device(), - params_flat, updates_flat, indices_flat); - OP_REQUIRES( - c, bad_i < 0, - errors::InvalidArgument( - "indices", SliceDebugString(indices.shape(), bad_i), " = ", - indices_flat(bad_i), " is not in [0, ", params.dim_size(0), ")")); + + if (TensorShapeUtils::IsScalar(updates.shape()) || + IsLegacyScalar(updates.shape())) { + const auto update = updates.scalar(); + functor::ScatterScalarFunctor functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, update, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params.dim_size(0), ")")); + } else { + auto updates_flat = + updates.shaped({N, updates.NumElements() / N}); + + functor::ScatterFunctor functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, updates_flat, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params.dim_size(0), ")")); + } } } }; @@ -195,16 +211,31 @@ class ScatterUpdateOp : public OpKernel { auto indices_flat = indices_host.flat(); auto params_flat = params.flat_outer_dims(); - auto updates_flat = updates.shaped({N, updates.NumElements() / N}); - - functor::ScatterFunctorSYCL functor; - const Index bad_i = functor(c, c->template eigen_device(), - params_flat, updates_flat, indices_flat); - OP_REQUIRES( - c, bad_i < 0, - errors::InvalidArgument( - "indices", SliceDebugString(indices.shape(), bad_i), " = ", - indices_flat(bad_i), " is not in [0, ", params.dim_size(0), ")")); + + if (TensorShapeUtils::IsScalar(updates.shape())) { + const auto update = updates.scalar(); + + functor::ScatterScalarFunctorSYCL functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, update, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params.dim_size(0), ")")); + } else { + auto updates_flat = + updates.shaped({N, updates.NumElements() / N}); + + functor::ScatterFunctorSYCL functor; + const Index bad_i = functor(c, c->template eigen_device(), + params_flat, updates_flat, indices_flat); + OP_REQUIRES(c, bad_i < 0, + errors::InvalidArgument( + "indices", SliceDebugString(indices.shape(), bad_i), + " = ", indices_flat(bad_i), " is not in [0, ", + params.dim_size(0), ")")); + } } } }; @@ -221,54 +252,71 @@ class ScatterUpdateOp : public OpKernel { REGISTER_SCATTER_KERNEL_INDEX(type, int32, dev, name, op); \ REGISTER_SCATTER_KERNEL_INDEX(type, int64, dev, name, op); -#define REGISTER_SCATTER_ARITHEMTIC(type, dev) \ +#define REGISTER_SCATTER_ARITHMETIC(type, dev) \ REGISTER_SCATTER_KERNEL(type, dev, "ScatterAdd", scatter_op::UpdateOp::ADD); \ REGISTER_SCATTER_KERNEL(type, dev, "ScatterDiv", scatter_op::UpdateOp::DIV); \ REGISTER_SCATTER_KERNEL(type, dev, "ScatterMul", scatter_op::UpdateOp::MUL); \ REGISTER_SCATTER_KERNEL(type, dev, "ScatterSub", scatter_op::UpdateOp::SUB); +#define REGISTER_SCATTER_MINMAX(type, dev) \ + REGISTER_SCATTER_KERNEL(type, dev, "ScatterMin", scatter_op::UpdateOp::MIN); \ + REGISTER_SCATTER_KERNEL(type, dev, "ScatterMax", scatter_op::UpdateOp::MAX); + #define REGISTER_SCATTER_UPDATE(type, dev) \ REGISTER_SCATTER_KERNEL(type, dev, "ScatterUpdate", \ scatter_op::UpdateOp::ASSIGN); // Registers CPU kernels. -#define REGISTER_SCATTER_ARITHEMTIC_CPU(type) \ - REGISTER_SCATTER_ARITHEMTIC(type, CPU); +#define REGISTER_SCATTER_ARITHMETIC_CPU(type) \ + REGISTER_SCATTER_ARITHMETIC(type, CPU); + +#define REGISTER_SCATTER_MINMAX_CPU(type) REGISTER_SCATTER_MINMAX(type, CPU); #define REGISTER_SCATTER_UPDATE_CPU(type) REGISTER_SCATTER_UPDATE(type, CPU); -TF_CALL_NUMBER_TYPES(REGISTER_SCATTER_ARITHEMTIC_CPU); +TF_CALL_REAL_NUMBER_TYPES(REGISTER_SCATTER_MINMAX_CPU); +TF_CALL_NUMBER_TYPES(REGISTER_SCATTER_ARITHMETIC_CPU); TF_CALL_ALL_TYPES(REGISTER_SCATTER_UPDATE_CPU); // Registers GPU kernels. #if GOOGLE_CUDA -#define REGISTER_SCATTER_ARITHEMTIC_GPU(type) \ - REGISTER_SCATTER_ARITHEMTIC(type, GPU); +#define REGISTER_SCATTER_ARITHMETIC_GPU(type) \ + REGISTER_SCATTER_ARITHMETIC(type, GPU); + +#define REGISTER_SCATTER_MINMAX_GPU(type) REGISTER_SCATTER_MINMAX(type, GPU); #define REGISTER_SCATTER_UPDATE_GPU(type) REGISTER_SCATTER_UPDATE(type, GPU); -TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHEMTIC_GPU); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHMETIC_GPU); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_MINMAX_GPU); TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_UPDATE_GPU); #endif // GOOGLE_CUDA // Registers GPU kernels. #if TENSORFLOW_USE_SYCL -#define REGISTER_SCATTER_ARITHEMTIC_SYCL(type) \ - REGISTER_SCATTER_ARITHEMTIC(type, SYCL); +#define REGISTER_SCATTER_ARITHMETIC_SYCL(type) \ + REGISTER_SCATTER_ARITHMETIC(type, SYCL); + +#define REGISTER_SCATTER_MINMAX_SYCL(type) REGISTER_SCATTER_MINMAX(type, SYCL); #define REGISTER_SCATTER_UPDATE_SYCL(type) REGISTER_SCATTER_UPDATE(type, SYCL); -TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHEMTIC_SYCL); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_ARITHMETIC_SYCL); +TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_MINMAX_SYCL); TF_CALL_GPU_NUMBER_TYPES_NO_HALF(REGISTER_SCATTER_UPDATE_SYCL); -#undef REGISTER_SCATTER_ARITHEMTIC_SYCL +#undef REGISTER_SCATTER_ARITHMETIC_SYCL +#undef REGISTER_SCATTER_MINMAX_SYCL #undef REGISTER_SCATTER_UPDATE_SYCL #endif // TENSORFLOW_USE_SYCL -#undef REGISTER_SCATTER_ARITHEMTIC -#undef REGISTER_SCATTER_ARITHEMTIC_CPU -#undef REGISTER_SCATTER_ARITHEMTIC_GPU +#undef REGISTER_SCATTER_ARITHMETIC +#undef REGISTER_SCATTER_ARITHMETIC_CPU +#undef REGISTER_SCATTER_ARITHMETIC_GPU +#undef REGISTER_SCATTER_MINMAX +#undef REGISTER_SCATTER_MINMAX_CPU +#undef REGISTER_SCATTER_MINMAX_GPU #undef REGISTER_SCATTER_UPDATE #undef REGISTER_SCATTER_UPDATE_CPU #undef REGISTER_SCATTER_UPDATE_GPU diff --git a/tensorflow/core/kernels/scatter_op_gpu.cu.cc b/tensorflow/core/kernels/scatter_op_gpu.cu.cc index 0b43704846..0df329310f 100644 --- a/tensorflow/core/kernels/scatter_op_gpu.cu.cc +++ b/tensorflow/core/kernels/scatter_op_gpu.cu.cc @@ -24,15 +24,18 @@ namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; // Instantiates functor specializations for GPU. -#define DEFINE_GPU_SPECS_OP(T, Index, op) \ - template struct functor::ScatterFunctor; +#define DEFINE_GPU_SPECS_OP(T, Index, op) \ + template struct functor::ScatterFunctor; \ + template struct functor::ScatterScalarFunctor; #define DEFINE_GPU_SPECS_INDEX(T, Index) \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ASSIGN); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::ADD); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::SUB); \ DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MUL); \ - DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::DIV); \ + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MIN); \ + DEFINE_GPU_SPECS_OP(T, Index, scatter_op::UpdateOp::MAX); #define DEFINE_GPU_SPECS(T) \ DEFINE_GPU_SPECS_INDEX(T, int32); \ diff --git a/tensorflow/core/kernels/scatter_op_test.cc b/tensorflow/core/kernels/scatter_op_test.cc index 0b8645a2ae..5b3537b94c 100644 --- a/tensorflow/core/kernels/scatter_op_test.cc +++ b/tensorflow/core/kernels/scatter_op_test.cc @@ -185,7 +185,7 @@ TEST_F(ScatterUpdateOpTest, Error_WrongDimsIndices) { Status s = RunOpKernel(); EXPECT_TRUE(StringPiece(s.ToString()) .contains("Must have updates.shape = indices.shape + " - "params.shape[1:], got ")) + "params.shape[1:] or updates.shape = [], got ")) << s; } @@ -202,7 +202,7 @@ TEST_F(ScatterUpdateOpTest, Error_MismatchedParamsAndUpdateDimensions) { Status s = RunOpKernel(); EXPECT_TRUE(StringPiece(s.ToString()) .contains("Must have updates.shape = indices.shape + " - "params.shape[1:], got ")) + "params.shape[1:] or updates.shape = [], got ")) << s; } @@ -219,7 +219,7 @@ TEST_F(ScatterUpdateOpTest, Error_MismatchedIndicesAndUpdateDimensions) { Status s = RunOpKernel(); EXPECT_TRUE(StringPiece(s.ToString()) .contains("Must have updates.shape = indices.shape + " - "params.shape[1:], got ")) + "params.shape[1:] or updates.shape = [], got ")) << s; } @@ -300,6 +300,20 @@ static void BM_ScatterDivInt64(int iters, int embedding_size) { BM_ScatterHelper(iters, embedding_size, "ScatterDiv"); } +static void BM_ScatterMinInt32(int iters, int embedding_size) { + BM_ScatterHelper(iters, embedding_size, "ScatterMin"); +} +static void BM_ScatterMinInt64(int iters, int embedding_size) { + BM_ScatterHelper(iters, embedding_size, "ScatterMin"); +} + +static void BM_ScatterMaxInt32(int iters, int embedding_size) { + BM_ScatterHelper(iters, embedding_size, "ScatterMax"); +} +static void BM_ScatterMaxInt64(int iters, int embedding_size) { + BM_ScatterHelper(iters, embedding_size, "ScatterMax"); +} + BENCHMARK(BM_ScatterUpdateInt32) ->Arg(1) ->Arg(10) @@ -332,5 +346,11 @@ BENCHMARK(BM_ScatterMulInt64)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); BENCHMARK(BM_ScatterDivInt32)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); BENCHMARK(BM_ScatterDivInt64)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); +BENCHMARK(BM_ScatterMinInt32)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); +BENCHMARK(BM_ScatterMinInt64)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); + +BENCHMARK(BM_ScatterMaxInt32)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); +BENCHMARK(BM_ScatterMaxInt64)->Arg(1)->Arg(10)->Arg(64)->Arg(256)->Arg(1024); + } // namespace } // namespace tensorflow diff --git a/tensorflow/core/ops/resource_variable_ops.cc b/tensorflow/core/ops/resource_variable_ops.cc index 0d8cf78cc2..3d0a6c2157 100644 --- a/tensorflow/core/ops/resource_variable_ops.cc +++ b/tensorflow/core/ops/resource_variable_ops.cc @@ -167,27 +167,75 @@ REGISTER_OP("ResourceGather") return Status::OK(); }); +namespace { + +Status ResourceScatterUpdateShape(InferenceContext* c) { + ShapeAndType handle_shape_and_type; + TF_RETURN_IF_ERROR(ValidateVariableResourceHandle(c, &handle_shape_and_type)); + ShapeHandle var_shape = handle_shape_and_type.shape; + ShapeHandle indices_shape = c->input(1); + + ShapeHandle unused_updates_shape; + ShapeHandle concat; + ShapeHandle var_subshape; + TF_RETURN_IF_ERROR(c->Subshape(var_shape, 1, &var_subshape)); + TF_RETURN_IF_ERROR(c->Concatenate(indices_shape, var_subshape, &concat)); + TF_RETURN_IF_ERROR( + InferenceContext::Rank(c->input(2)) == 0 + ? Status::OK() + : c->Merge(c->input(2), concat, &unused_updates_shape)); + return Status::OK(); +} + +} // namespace + REGISTER_OP("ResourceScatterAdd") .Input("resource: resource") .Input("indices: Tindices") .Input("updates: dtype") .Attr("dtype: numbertype") .Attr("Tindices: {int32, int64}") - .SetShapeFn([](InferenceContext* c) { - ShapeAndType handle_shape_and_type; - TF_RETURN_IF_ERROR( - ValidateVariableResourceHandle(c, &handle_shape_and_type)); - ShapeHandle var_shape = handle_shape_and_type.shape; - ShapeHandle indices_shape = c->input(1); + .SetShapeFn(ResourceScatterUpdateShape); - ShapeHandle unused_updates_shape; - ShapeHandle concat; - ShapeHandle var_subshape; - TF_RETURN_IF_ERROR(c->Subshape(var_shape, 1, &var_subshape)); - TF_RETURN_IF_ERROR(c->Concatenate(indices_shape, var_subshape, &concat)); - TF_RETURN_IF_ERROR(c->Merge(c->input(2), concat, &unused_updates_shape)); - return Status::OK(); - }); +REGISTER_OP("ResourceScatterSub") + .Input("resource: resource") + .Input("indices: Tindices") + .Input("updates: dtype") + .Attr("dtype: numbertype") + .Attr("Tindices: {int32, int64}") + .SetShapeFn(ResourceScatterUpdateShape); + +REGISTER_OP("ResourceScatterMul") + .Input("resource: resource") + .Input("indices: Tindices") + .Input("updates: dtype") + .Attr("dtype: numbertype") + .Attr("Tindices: {int32, int64}") + .SetShapeFn(ResourceScatterUpdateShape); + +REGISTER_OP("ResourceScatterDiv") + .Input("resource: resource") + .Input("indices: Tindices") + .Input("updates: dtype") + .Attr("dtype: numbertype") + .Attr("Tindices: {int32, int64}") + .SetShapeFn(ResourceScatterUpdateShape); + +REGISTER_OP("ResourceScatterMin") + .Input("resource: resource") + .Input("indices: Tindices") + .Input("updates: dtype") + .Attr("dtype: numbertype") + .Attr("Tindices: {int32, int64}") + .SetShapeFn(ResourceScatterUpdateShape); + +REGISTER_OP("ResourceScatterMax") + .Input("resource: resource") + .Input("indices: Tindices") + .Input("updates: dtype") + .Attr("dtype: numbertype") + .Attr("Tindices: {int32, int64}") + .SetShapeFn(ResourceScatterUpdateShape); REGISTER_OP("ResourceScatterUpdate") .Input("resource: resource") @@ -195,21 +243,7 @@ REGISTER_OP("ResourceScatterUpdate") .Input("updates: dtype") .Attr("dtype: type") .Attr("Tindices: {int32, int64}") - .SetShapeFn([](InferenceContext* c) { - ShapeAndType handle_shape_and_type; - TF_RETURN_IF_ERROR( - ValidateVariableResourceHandle(c, &handle_shape_and_type)); - ShapeHandle var_shape = handle_shape_and_type.shape; - ShapeHandle indices_shape = c->input(1); - - ShapeHandle unused_updates_shape; - ShapeHandle concat; - ShapeHandle var_subshape; - TF_RETURN_IF_ERROR(c->Subshape(var_shape, 1, &var_subshape)); - TF_RETURN_IF_ERROR(c->Concatenate(indices_shape, var_subshape, &concat)); - TF_RETURN_IF_ERROR(c->Merge(c->input(2), concat, &unused_updates_shape)); - return Status::OK(); - }); + .SetShapeFn(ResourceScatterUpdateShape); REGISTER_OP("MutexV2") .Attr("container: string = ''") diff --git a/tensorflow/core/ops/state_ops.cc b/tensorflow/core/ops/state_ops.cc index 7a524b60c0..664f52452e 100644 --- a/tensorflow/core/ops/state_ops.cc +++ b/tensorflow/core/ops/state_ops.cc @@ -122,7 +122,10 @@ Status ScatterUpdateShape(InferenceContext* c) { ShapeHandle var_subshape; TF_RETURN_IF_ERROR(c->Subshape(var_shape, 1, &var_subshape)); TF_RETURN_IF_ERROR(c->Concatenate(indices_shape, var_subshape, &concat)); - TF_RETURN_IF_ERROR(c->Merge(c->input(2), concat, &unused_updates_shape)); + TF_RETURN_IF_ERROR( + InferenceContext::Rank(c->input(2)) == 0 + ? Status::OK() + : c->Merge(c->input(2), concat, &unused_updates_shape)); c->set_output(0, var_shape); return Status::OK(); @@ -180,6 +183,26 @@ REGISTER_OP("ScatterDiv") .Attr("use_locking: bool = false") .SetShapeFn(ScatterUpdateShape); +REGISTER_OP("ScatterMin") + .Input("ref: Ref(T)") + .Input("indices: Tindices") + .Input("updates: T") + .Output("output_ref: Ref(T)") + .Attr("T: {half, bfloat16, float, double, int32, int64}") + .Attr("Tindices: {int32, int64}") + .Attr("use_locking: bool = false") + .SetShapeFn(ScatterUpdateShape); + +REGISTER_OP("ScatterMax") + .Input("ref: Ref(T)") + .Input("indices: Tindices") + .Input("updates: T") + .Output("output_ref: Ref(T)") + .Attr("T: {half, bfloat16, float, double, int32, int64}") + .Attr("Tindices: {int32, int64}") + .Attr("use_locking: bool = false") + .SetShapeFn(ScatterUpdateShape); + REGISTER_OP("ScatterNdUpdate") .Input("ref: Ref(T)") .Input("indices: Tindices") diff --git a/tensorflow/docs_src/api_guides/python/state_ops.md b/tensorflow/docs_src/api_guides/python/state_ops.md index 0d612ee0c7..ec2d877386 100644 --- a/tensorflow/docs_src/api_guides/python/state_ops.md +++ b/tensorflow/docs_src/api_guides/python/state_ops.md @@ -83,6 +83,8 @@ automatically by the optimizers in most cases. * @{tf.scatter_sub} * @{tf.scatter_mul} * @{tf.scatter_div} +* @{tf.scatter_min} +* @{tf.scatter_max} * @{tf.scatter_nd_update} * @{tf.scatter_nd_add} * @{tf.scatter_nd_sub} diff --git a/tensorflow/python/kernel_tests/resource_variable_ops_test.py b/tensorflow/python/kernel_tests/resource_variable_ops_test.py index 563eeff2a6..742564f9bf 100644 --- a/tensorflow/python/kernel_tests/resource_variable_ops_test.py +++ b/tensorflow/python/kernel_tests/resource_variable_ops_test.py @@ -185,6 +185,204 @@ class ResourceVariableOpsTest(test_util.TensorFlowTestCase): read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) self.assertEqual(self.evaluate(read), [[3]]) + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterSub(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[1]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_sub(handle, [0], + constant_op.constant( + [[2]], + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[-1]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMul(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[1]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_mul(handle, [0], + constant_op.constant( + [[5]], + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[5]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterDiv(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_div(handle, [0], + constant_op.constant( + [[3]], + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[2]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMin(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_min(handle, [0], + constant_op.constant( + [[3]], + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[3]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMax(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_max(handle, [0], + constant_op.constant( + [[3]], + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[6]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterAddScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[1]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_add(handle, [0], + constant_op.constant( + 2, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[3]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterSubScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[1]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_sub(handle, [0], + constant_op.constant( + 2, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[-1]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMulScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[1]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_mul(handle, [0], + constant_op.constant( + 5, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[5]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterDivScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_div(handle, [0], + constant_op.constant( + 3, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[2]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMinScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_min(handle, [0], + constant_op.constant( + 3, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[3]]) + + @test_util.run_in_graph_and_eager_modes(use_gpu=True) + def testScatterMaxScalar(self): + with ops.device("cpu:0"): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.int32, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [[6]], + dtype=dtypes.int32))) + self.evaluate( + resource_variable_ops.resource_scatter_max(handle, [0], + constant_op.constant( + 3, + dtype=dtypes.int32))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.int32) + self.assertEqual(self.evaluate(read), [[6]]) + def testScatterUpdateString(self): handle = resource_variable_ops.var_handle_op( dtype=dtypes.string, shape=[1, 1]) @@ -196,6 +394,23 @@ class ResourceVariableOpsTest(test_util.TensorFlowTestCase): self.assertEqual(compat.as_bytes(self.evaluate(read)[0][0]), compat.as_bytes("b")) + def testScatterUpdateStringScalar(self): + handle = resource_variable_ops.var_handle_op( + dtype=dtypes.string, shape=[1, 1]) + self.evaluate( + resource_variable_ops.assign_variable_op(handle, + constant_op.constant( + [["a"]], + dtype=dtypes.string))) + self.evaluate( + resource_variable_ops.resource_scatter_update(handle, [0], + constant_op.constant( + "b", + dtype=dtypes.string))) + read = resource_variable_ops.read_variable_op(handle, dtype=dtypes.string) + self.assertEqual( + compat.as_bytes(self.evaluate(read)[0][0]), compat.as_bytes("b")) + # TODO(alive): get this to work in Eager mode. def testGPU(self): with self.test_session(use_gpu=True): diff --git a/tensorflow/python/kernel_tests/scatter_ops_test.py b/tensorflow/python/kernel_tests/scatter_ops_test.py index 7cdf11d884..c70a4ffce7 100644 --- a/tensorflow/python/kernel_tests/scatter_ops_test.py +++ b/tensorflow/python/kernel_tests/scatter_ops_test.py @@ -38,38 +38,100 @@ def _NumpyAdd(ref, indices, updates): ref[indx] += updates[i] +def _NumpyAddScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] += update + + def _NumpySub(ref, indices, updates): for i, indx in np.ndenumerate(indices): ref[indx] -= updates[i] +def _NumpySubScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] -= update + + def _NumpyMul(ref, indices, updates): for i, indx in np.ndenumerate(indices): ref[indx] *= updates[i] +def _NumpyMulScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] *= update + + def _NumpyDiv(ref, indices, updates): for i, indx in np.ndenumerate(indices): ref[indx] /= updates[i] +def _NumpyDivScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] /= update + + +def _NumpyMin(ref, indices, updates): + for i, indx in np.ndenumerate(indices): + ref[indx] = np.minimum(ref[indx], updates[i]) + + +def _NumpyMinScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] = np.minimum(ref[indx], update) + + +def _NumpyMax(ref, indices, updates): + for i, indx in np.ndenumerate(indices): + ref[indx] = np.maximum(ref[indx], updates[i]) + + +def _NumpyMaxScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] = np.maximum(ref[indx], update) + + def _NumpyUpdate(ref, indices, updates): for i, indx in np.ndenumerate(indices): ref[indx] = updates[i] +def _NumpyUpdateScalar(ref, indices, update): + for _, indx in np.ndenumerate(indices): + ref[indx] = update + + _TF_OPS_TO_NUMPY = { state_ops.scatter_update: _NumpyUpdate, state_ops.scatter_add: _NumpyAdd, state_ops.scatter_sub: _NumpySub, state_ops.scatter_mul: _NumpyMul, state_ops.scatter_div: _NumpyDiv, + state_ops.scatter_min: _NumpyMin, + state_ops.scatter_max: _NumpyMax, +} + +_TF_OPS_TO_NUMPY_SCALAR = { + state_ops.scatter_update: _NumpyUpdateScalar, + state_ops.scatter_add: _NumpyAddScalar, + state_ops.scatter_sub: _NumpySubScalar, + state_ops.scatter_mul: _NumpyMulScalar, + state_ops.scatter_div: _NumpyDivScalar, + state_ops.scatter_min: _NumpyMinScalar, + state_ops.scatter_max: _NumpyMaxScalar, } class ScatterTest(test.TestCase): - def _VariableRankTest(self, tf_scatter, vtype, itype, repeat_indices=False): + def _VariableRankTest(self, + tf_scatter, + vtype, + itype, + repeat_indices=False, + updates_are_scalar=False): np.random.seed(8) with self.test_session(use_gpu=True): for indices_shape in (), (2,), (3, 7), (3, 4, 7): @@ -89,8 +151,11 @@ class ScatterTest(test.TestCase): indices[np.random.randint(size // 2)]) np.random.shuffle(indices) indices = indices.reshape(indices_shape) - updates = _AsType( - np.random.randn(*(indices_shape + extra_shape)), vtype) + if updates_are_scalar: + updates = _AsType(np.random.randn(), vtype) + else: + updates = _AsType( + np.random.randn(*(indices_shape + extra_shape)), vtype) # Clips small values to avoid division by zero. def clip_small_values(x): @@ -101,7 +166,10 @@ class ScatterTest(test.TestCase): # Scatter via numpy new = old.copy() - np_scatter = _TF_OPS_TO_NUMPY[tf_scatter] + if updates_are_scalar: + np_scatter = _TF_OPS_TO_NUMPY_SCALAR[tf_scatter] + else: + np_scatter = _TF_OPS_TO_NUMPY[tf_scatter] np_scatter(new, indices, updates) # Scatter via tensorflow ref = variables.Variable(old) @@ -109,25 +177,35 @@ class ScatterTest(test.TestCase): tf_scatter(ref, indices, updates).eval() self.assertAllClose(ref.eval(), new) - def _VariableRankTests(self, tf_scatter, repeat_indices=False): + def _VariableRankTests(self, + tf_scatter, + repeat_indices=False, + updates_are_scalar=False): for vtype in (np.float32, np.float64): for itype in (np.int32, np.int64): - self._VariableRankTest(tf_scatter, vtype, itype, repeat_indices) + self._VariableRankTest(tf_scatter, vtype, itype, repeat_indices, + updates_are_scalar) def testVariableRankUpdate(self): - self._VariableRankTests(state_ops.scatter_update) + self._VariableRankTests(state_ops.scatter_update, False) def testVariableRankAdd(self): - self._VariableRankTests(state_ops.scatter_add) + self._VariableRankTests(state_ops.scatter_add, False) def testVariableRankSub(self): - self._VariableRankTests(state_ops.scatter_sub) + self._VariableRankTests(state_ops.scatter_sub, False) def testVariableRankMul(self): - self._VariableRankTests(state_ops.scatter_mul) + self._VariableRankTests(state_ops.scatter_mul, False) def testVariableRankDiv(self): - self._VariableRankTests(state_ops.scatter_div) + self._VariableRankTests(state_ops.scatter_div, False) + + def testVariableRankMin(self): + self._VariableRankTests(state_ops.scatter_min, False) + + def testVariableRankMax(self): + self._VariableRankTests(state_ops.scatter_max, False) def testRepeatIndicesAdd(self): self._VariableRankTests(state_ops.scatter_add, True) @@ -141,6 +219,51 @@ class ScatterTest(test.TestCase): def testRepeatIndicesDiv(self): self._VariableRankTests(state_ops.scatter_div, True) + def testRepeatIndicesMin(self): + self._VariableRankTests(state_ops.scatter_min, True) + + def testRepeatIndicesMax(self): + self._VariableRankTests(state_ops.scatter_max, True) + + def testVariableRankUpdateScalar(self): + self._VariableRankTests(state_ops.scatter_update, False, True) + + def testVariableRankAddScalar(self): + self._VariableRankTests(state_ops.scatter_add, False, True) + + def testVariableRankSubScalar(self): + self._VariableRankTests(state_ops.scatter_sub, False, True) + + def testVariableRankMulScalar(self): + self._VariableRankTests(state_ops.scatter_mul, False, True) + + def testVariableRankDivScalar(self): + self._VariableRankTests(state_ops.scatter_div, False, True) + + def testVariableRankMinScalar(self): + self._VariableRankTests(state_ops.scatter_min, False, True) + + def testVariableRankMaxScalar(self): + self._VariableRankTests(state_ops.scatter_max, False, True) + + def testRepeatIndicesAddScalar(self): + self._VariableRankTests(state_ops.scatter_add, True, True) + + def testRepeatIndicesSubScalar(self): + self._VariableRankTests(state_ops.scatter_sub, True, True) + + def testRepeatIndicesMulScalar(self): + self._VariableRankTests(state_ops.scatter_mul, True, True) + + def testRepeatIndicesDivScalar(self): + self._VariableRankTests(state_ops.scatter_div, True, True) + + def testRepeatIndicesMinScalar(self): + self._VariableRankTests(state_ops.scatter_min, True, True) + + def testRepeatIndicesMaxScalar(self): + self._VariableRankTests(state_ops.scatter_max, True, True) + def testBooleanScatterUpdate(self): if not test.is_gpu_available(): with self.test_session(use_gpu=False) as session: diff --git a/tensorflow/python/ops/standard_ops.py b/tensorflow/python/ops/standard_ops.py index 230b7ef937..e90ff0746a 100644 --- a/tensorflow/python/ops/standard_ops.py +++ b/tensorflow/python/ops/standard_ops.py @@ -80,6 +80,8 @@ from tensorflow.python.ops.state_ops import scatter_add from tensorflow.python.ops.state_ops import scatter_div from tensorflow.python.ops.state_ops import scatter_mul from tensorflow.python.ops.state_ops import scatter_sub +from tensorflow.python.ops.state_ops import scatter_min +from tensorflow.python.ops.state_ops import scatter_max from tensorflow.python.ops.state_ops import scatter_update from tensorflow.python.ops.state_ops import scatter_nd_add from tensorflow.python.ops.state_ops import scatter_nd_sub diff --git a/tensorflow/python/ops/state_ops.py b/tensorflow/python/ops/state_ops.py index c3ad5831b4..01fc3182bc 100644 --- a/tensorflow/python/ops/state_ops.py +++ b/tensorflow/python/ops/state_ops.py @@ -63,6 +63,8 @@ @@scatter_nd_update @@scatter_sub @@scatter_update +@@scatter_min +@@scatter_max @@sparse_mask @@tables_initializer @@trainable_variables diff --git a/tensorflow/tools/api/golden/tensorflow.pbtxt b/tensorflow/tools/api/golden/tensorflow.pbtxt index 55b82dd765..937044aece 100644 --- a/tensorflow/tools/api/golden/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.pbtxt @@ -1688,6 +1688,14 @@ tf_module { name: "scatter_div" argspec: "args=[\'ref\', \'indices\', \'updates\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " } + member_method { + name: "scatter_max" + argspec: "args=[\'ref\', \'indices\', \'updates\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + } + member_method { + name: "scatter_min" + argspec: "args=[\'ref\', \'indices\', \'updates\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + } member_method { name: "scatter_mul" argspec: "args=[\'ref\', \'indices\', \'updates\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " -- GitLab From dd3adb6165605c28f1a993f9093e8f7c99b357c5 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 16:13:13 -0700 Subject: [PATCH 180/906] [XLA] Redesign: implement local client and local service interface. PiperOrigin-RevId: 190291400 --- .../compiler/xla/client/local_client.cc | 18 +++ tensorflow/compiler/xla/client/local_client.h | 9 +- tensorflow/compiler/xla/service/BUILD | 1 + .../compiler/xla/service/local_service.cc | 153 ++++++++++++++---- .../compiler/xla/service/local_service.h | 13 ++ tensorflow/compiler/xla/service/service.cc | 41 +++-- tensorflow/compiler/xla/service/service.h | 11 ++ 7 files changed, 205 insertions(+), 41 deletions(-) diff --git a/tensorflow/compiler/xla/client/local_client.cc b/tensorflow/compiler/xla/client/local_client.cc index 91396f055f..30594243dc 100644 --- a/tensorflow/compiler/xla/client/local_client.cc +++ b/tensorflow/compiler/xla/client/local_client.cc @@ -265,6 +265,24 @@ StatusOr> LocalClient::Compile( updated_options)); } +StatusOr> LocalClient::Compile( + const XlaComputation& computation, + const tensorflow::gtl::ArraySlice argument_layouts, + const ExecutableBuildOptions& options) { + ExecutableBuildOptions updated_options = options; + if (options.device_ordinal() == -1) { + updated_options.set_device_ordinal(default_device_ordinal()); + VLOG(3) << "Set device ordinal to default value of: " + << updated_options.device_ordinal(); + } + TF_ASSIGN_OR_RETURN(std::unique_ptr executable, + local_service_->CompileExecutable( + computation, argument_layouts, updated_options)); + return WrapUnique(new LocalExecutable(std::move(executable), + local_service_->mutable_backend(), + updated_options)); +} + StatusOr> LocalClient::LiteralToShapedBuffer(const Literal& literal, int device_ordinal, DeviceMemoryAllocator* allocator) { diff --git a/tensorflow/compiler/xla/client/local_client.h b/tensorflow/compiler/xla/client/local_client.h index 2e5d85ba68..98ee7c62c9 100644 --- a/tensorflow/compiler/xla/client/local_client.h +++ b/tensorflow/compiler/xla/client/local_client.h @@ -123,7 +123,14 @@ class LocalClient : public Client { const tensorflow::gtl::ArraySlice argument_layouts, const ExecutableBuildOptions& options); - // TODO(b/74197823): Add a overload of Compile for XlaComputation. + // Build and return a LocalExecutable object. The executable is compiled using + // the given XlaComputation, argument layouts and options. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> Compile( + const XlaComputation& computation, + const tensorflow::gtl::ArraySlice argument_layouts, + const ExecutableBuildOptions& options); // Copy the literal data to the device with the given ordinal and return as a // ScopedShapedBuffer. If non-null the given memory allocator is used for diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index d4d67872cf..da16976d06 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -623,6 +623,7 @@ cc_library( "//tensorflow/compiler/xla:util", "//tensorflow/compiler/xla:xla_data_proto", "//tensorflow/compiler/xla/client:executable_build_options", + "//tensorflow/compiler/xla/client/xla_client:xla_computation", "//tensorflow/core:lib", "//tensorflow/core:stream_executor_no_cuda", ], diff --git a/tensorflow/compiler/xla/service/local_service.cc b/tensorflow/compiler/xla/service/local_service.cc index 1e2d8eea58..499f280211 100644 --- a/tensorflow/compiler/xla/service/local_service.cc +++ b/tensorflow/compiler/xla/service/local_service.cc @@ -69,6 +69,68 @@ LocalService::LocalService(const ServiceOptions& options, std::unique_ptr execute_backend) : Service(options, std::move(execute_backend)) {} +namespace { + +// Retrieves the parameter metadata for the given computation and parameter +// number. +// +// If the parameter number is invalid for this computation, nullopt is +// returned. When the return value has_value(), nullptr will never be +// the held value. +tensorflow::gtl::optional ParameterMetadata( + const XlaComputation& computation, int parameter_number) { + for (const HloComputationProto& comp : computation.proto().computations()) { + if (comp.id() == computation.proto().entry_computation_id()) { + for (const HloInstructionProto& instr : comp.instructions()) { + if (instr.opcode() == HloOpcodeString(HloOpcode::kParameter) && + instr.parameter_number() == parameter_number) { + if (!instr.has_metadata()) { + return tensorflow::gtl::nullopt; + } + return &instr.metadata(); + } + } + } + } + return tensorflow::gtl::nullopt; +} + +ExecutionOptions CreateExecutionOptions( + const ExecutableBuildOptions& build_options, + const ProgramShape* program_shape) { + ExecutionOptions execution_options = CreateDefaultExecutionOptions(); + if (build_options.hlo_profile().has_value()) { + execution_options.mutable_debug_options()->set_xla_hlo_profile( + *build_options.hlo_profile()); + } + if (build_options.generate_hlo_graph().has_value()) { + execution_options.mutable_debug_options()->set_xla_generate_hlo_graph( + build_options.generate_hlo_graph().value()); + } + if (build_options.dump_optimized_hlo_proto_to().has_value()) { + execution_options.mutable_debug_options() + ->set_xla_dump_optimized_hlo_proto_to( + build_options.dump_optimized_hlo_proto_to().value()); + } + if (build_options.dump_per_pass_hlo_proto_to().has_value()) { + execution_options.mutable_debug_options() + ->set_xla_dump_per_pass_hlo_proto_to( + build_options.dump_per_pass_hlo_proto_to().value()); + } + if (build_options.result_layout() != nullptr) { + *execution_options.mutable_shape_with_output_layout() = + *build_options.result_layout(); + } else { + *execution_options.mutable_shape_with_output_layout() = + program_shape->result(); + LayoutUtil::SetToDefaultLayout( + execution_options.mutable_shape_with_output_layout()); + } + return execution_options; +} + +} // namespace + StatusOr> LocalService::CompileExecutable( const ComputationHandle& computation, const tensorflow::gtl::ArraySlice argument_layouts, @@ -118,34 +180,8 @@ StatusOr> LocalService::CompileExecutable( *build_options.result_layout(), program_shape->result())); } - ExecutionOptions execution_options = CreateDefaultExecutionOptions(); - if (build_options.hlo_profile().has_value()) { - execution_options.mutable_debug_options()->set_xla_hlo_profile( - *build_options.hlo_profile()); - } - if (build_options.generate_hlo_graph().has_value()) { - execution_options.mutable_debug_options()->set_xla_generate_hlo_graph( - build_options.generate_hlo_graph().value()); - } - if (build_options.dump_optimized_hlo_proto_to().has_value()) { - execution_options.mutable_debug_options() - ->set_xla_dump_optimized_hlo_proto_to( - build_options.dump_optimized_hlo_proto_to().value()); - } - if (build_options.dump_per_pass_hlo_proto_to().has_value()) { - execution_options.mutable_debug_options() - ->set_xla_dump_per_pass_hlo_proto_to( - build_options.dump_per_pass_hlo_proto_to().value()); - } - if (build_options.result_layout() != nullptr) { - *execution_options.mutable_shape_with_output_layout() = - *build_options.result_layout(); - } else { - *execution_options.mutable_shape_with_output_layout() = - program_shape->result(); - LayoutUtil::SetToDefaultLayout( - execution_options.mutable_shape_with_output_layout()); - } + ExecutionOptions execution_options = + CreateExecutionOptions(build_options, program_shape.get()); TF_ASSIGN_OR_RETURN(std::unique_ptr module_config, CreateModuleConfig(*program_shape, argument_layouts, &execution_options, user_computation)); @@ -159,6 +195,67 @@ StatusOr> LocalService::CompileExecutable( build_options.device_allocator()); } +StatusOr> LocalService::CompileExecutable( + const XlaComputation& computation, + const tensorflow::gtl::ArraySlice argument_layouts, + const ExecutableBuildOptions& build_options) { + const HloModuleProto& proto = computation.proto(); + TF_RET_CHECK(proto.has_program_shape()); + const ProgramShape& program_shape = proto.program_shape(); + + // Validate incoming layouts. + if (argument_layouts.size() != program_shape.parameters_size()) { + return InvalidArgument( + "Invalid number of arguments for computation: expected %d, got %zu.", + program_shape.parameters_size(), argument_layouts.size()); + } + + for (int i = 0; i < argument_layouts.size(); ++i) { + const Shape& argument_shape = *argument_layouts[i]; + TF_RETURN_IF_ERROR(ShapeUtil::ValidateShape(argument_shape)); + if (!ShapeUtil::Compatible(argument_shape, program_shape.parameters(i))) { + tensorflow::gtl::optional metadata = + ParameterMetadata(computation, /*parameter_number=*/i); + auto metadata_string = [&metadata]() -> string { + if (!metadata.has_value()) { + return ""; + } + CHECK(metadata.value() != nullptr); + const OpMetadata& m = *metadata.value(); + if (!m.source_file().empty()) { + return tensorflow::strings::Printf( + " (%s:%d)", m.source_file().c_str(), m.source_line()); + } + return ""; + }; + return InvalidArgument( + "Invalid argument shape for argument %d%s, expected %s, got %s.", i, + metadata_string().c_str(), + ShapeUtil::HumanString(program_shape.parameters(i)).c_str(), + ShapeUtil::HumanString(argument_shape).c_str()); + } + } + if (build_options.result_layout() != nullptr) { + TF_RETURN_IF_ERROR(ValidateResultShapeWithLayout( + *build_options.result_layout(), program_shape.result())); + } + + ExecutionOptions execution_options = + CreateExecutionOptions(build_options, &program_shape); + + TF_ASSIGN_OR_RETURN( + std::unique_ptr module_config, + CreateModuleConfig(program_shape, argument_layouts, &execution_options)); + + TF_ASSIGN_OR_RETURN( + se::StreamExecutor * executor, + execute_backend_->stream_executor(build_options.device_ordinal())); + + return BuildExecutable(proto, std::move(module_config), + execute_backend_.get(), executor, + build_options.device_allocator()); +} + StatusOr LocalService::ReplicaNumberToDeviceOrdinal(int replica_number) { return backend().computation_placer()->DeviceId( replica_number, /*computation=*/0, options_.number_of_replicas(), diff --git a/tensorflow/compiler/xla/service/local_service.h b/tensorflow/compiler/xla/service/local_service.h index 15e120685e..06567cabd6 100644 --- a/tensorflow/compiler/xla/service/local_service.h +++ b/tensorflow/compiler/xla/service/local_service.h @@ -19,6 +19,7 @@ limitations under the License. #include #include "tensorflow/compiler/xla/client/executable_build_options.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h" #include "tensorflow/compiler/xla/service/backend.h" #include "tensorflow/compiler/xla/service/compiler.h" #include "tensorflow/compiler/xla/service/device_memory_allocator.h" @@ -50,6 +51,18 @@ class LocalService : public Service { const tensorflow::gtl::ArraySlice argument_layouts, const ExecutableBuildOptions& options); + // Builds an Executable with the given XlaComputation, argument layouts and + // options. If result_layout is non-null, then the executable is compiled to + // produce a result of the given layout. If device_allocator is non-null, + // then the compiler may use it to allocate temp space on the device. The + // compiler is responsible for freeing any memory it allocates this way. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> CompileExecutable( + const XlaComputation& computation, + const tensorflow::gtl::ArraySlice argument_layouts, + const ExecutableBuildOptions& build_options); + // Returns the device ordinal that corresponds to the given replica number. // // This returns an error if there is not a one-to-one correspondence of diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 4f6a82333b..1d379f0d03 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -963,6 +963,30 @@ tensorflow::Status Service::Execute(const ExecuteRequest* arg, return tensorflow::Status::OK(); } +StatusOr> Service::BuildExecutable( + const HloModuleProto& module_proto, + std::unique_ptr module_config, Backend* backend, + se::StreamExecutor* executor, DeviceMemoryAllocator* device_allocator) { + VLOG(1) << Printf( + "BuildExecutable on service %p with serialized module proto: %s", this, + module_proto.name().c_str()); + + TF_ASSIGN_OR_RETURN(std::unique_ptr module, + HloModule::CreateFromProto(module_proto, *module_config)); + + TF_RETURN_IF_ERROR(MaybeDumpHloModule(*module)); + + TF_ASSIGN_OR_RETURN( + module, backend->compiler()->RunHloPasses(std::move(module), executor, + device_allocator)); + + TF_ASSIGN_OR_RETURN(std::unique_ptr executable, + backend->compiler()->RunBackend( + std::move(module), executor, device_allocator)); + + return std::move(executable); +} + tensorflow::Status Service::ExecuteGraph(const ExecuteGraphRequest* arg, ExecuteResponse* result) { VLOG(1) << "running execute-graph request"; @@ -979,24 +1003,17 @@ tensorflow::Status Service::ExecuteGraph(const ExecuteGraphRequest* arg, std::vector> replicated_arguments, ResolveAndValidateArguments(arg->arguments(), replicas)); - TF_ASSIGN_OR_RETURN(const auto& config, + TF_ASSIGN_OR_RETURN(std::unique_ptr module_config, CreateModuleConfig(arg->computation().program_shape(), replicated_arguments.front(), arg->execution_options())); - TF_ASSIGN_OR_RETURN(std::unique_ptr module, - HloModule::CreateFromProto(arg->computation(), *config)); - TF_RETURN_IF_ERROR(MaybeDumpHloModule(*module)); - - TF_ASSIGN_OR_RETURN(module, execute_backend_->compiler()->RunHloPasses( - std::move(module), - execute_backend_->default_stream_executor(), - /*device_allocator=*/nullptr)); TF_ASSIGN_OR_RETURN( std::unique_ptr executable, - execute_backend_->compiler()->RunBackend( - std::move(module), execute_backend_->default_stream_executor(), - /*device_allocator=*/nullptr)); + BuildExecutable(arg->computation(), std::move(module_config), + execute_backend_.get(), + execute_backend_->default_stream_executor(), + /*device_allocator=*/nullptr)); TF_ASSIGN_OR_RETURN( *result->mutable_output(), diff --git a/tensorflow/compiler/xla/service/service.h b/tensorflow/compiler/xla/service/service.h index 3b79920b0a..773f0a642d 100644 --- a/tensorflow/compiler/xla/service/service.h +++ b/tensorflow/compiler/xla/service/service.h @@ -115,6 +115,8 @@ class Service : public ServiceInterface { // Executes a computation with the provided global data passed as // immutable arguments. The request contains the whole computation graph. // Returns global data output and execution timing. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. tensorflow::Status ExecuteGraph(const ExecuteGraphRequest* arg, ExecuteResponse* result) override; @@ -299,6 +301,15 @@ class Service : public ServiceInterface { perftools::gputools::StreamExecutor* executor, DeviceMemoryAllocator* device_allocator = nullptr); + // Builds an Executable for the given HLO module proto. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> BuildExecutable( + const HloModuleProto& module_proto, + std::unique_ptr module_config, Backend* backend, + perftools::gputools::StreamExecutor* executor, + DeviceMemoryAllocator* device_allocator = nullptr); + // Same as BuildExecutable() above, but builds a list of Executables for the // given computations that may interact with each other. StatusOr>> BuildExecutables( -- GitLab From f54f57337078c93877df5c9a1b126e879f5b33a5 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Fri, 23 Mar 2018 16:15:55 -0700 Subject: [PATCH 181/906] Moves TensorHandleCopyToDevice to TensorHandle::CopyToDevice. PiperOrigin-RevId: 190291768 --- tensorflow/c/eager/BUILD | 1 + tensorflow/c/eager/c_api.cc | 125 +----------------- tensorflow/core/common_runtime/eager/BUILD | 20 +++ .../eager/copy_to_device_node.h | 69 ++++++++++ .../common_runtime/eager/tensor_handle.cc | 71 ++++++++++ .../core/common_runtime/eager/tensor_handle.h | 3 + 6 files changed, 168 insertions(+), 121 deletions(-) create mode 100644 tensorflow/core/common_runtime/eager/copy_to_device_node.h diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index d2d8d59323..8df7b56623 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -32,6 +32,7 @@ tf_cuda_library( "//tensorflow/core/common_runtime/eager:eager_executor", "//tensorflow/core/common_runtime/eager:kernel_and_device", "//tensorflow/core/common_runtime/eager:tensor_handle", + "//tensorflow/core/common_runtime/eager:copy_to_device_node", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:framework", "//tensorflow/core:framework_internal", diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 59432f2ef8..c69635d529 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -32,6 +32,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/device_mgr.h" #include "tensorflow/core/common_runtime/device_set.h" +#include "tensorflow/core/common_runtime/eager/copy_to_device_node.h" #include "tensorflow/core/common_runtime/function.h" #include "tensorflow/core/common_runtime/rendezvous_mgr.h" #include "tensorflow/core/framework/node_def_util.h" @@ -213,82 +214,6 @@ TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status) { } } // extern "C" -namespace { - -tensorflow::Status TensorHandleCopyToDevice(tensorflow::TensorHandle* h, - TFE_Context* ctx, - tensorflow::Device* dstd, - tensorflow::TensorHandle** output) { - const tensorflow::Tensor* src = nullptr; - tensorflow::Device* srcd = nullptr; - // TODO(agarwal): src_opd is unused. Perhaps allow TensorAndDevice to accept - // nullptr. - tensorflow::Device* src_opd = nullptr; - TF_RETURN_IF_ERROR(h->TensorAndDevice(&src, &srcd, &src_opd)); - if (srcd == nullptr) srcd = ctx->context.HostCPU(); - bool is_same_device = - (srcd == dstd) || (DeviceName(srcd) == DeviceName(dstd)); - const bool dst_cpu = IsCPU(dstd); - const bool src_cpu = IsCPU(srcd); - // both_on_cpu can be true and yet is_same_device is false, if one of src/dst - // has device type XLA_CPU, and the other CPU. - const bool both_on_cpu = src_cpu && dst_cpu; - if (is_same_device || both_on_cpu) { - dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(*src, dstd, dstd); - return tensorflow::Status::OK(); - } - if (!dst_cpu && (src->dtype() != tensorflow::DT_VARIANT && - !tensorflow::DataTypeCanUseMemcpy(src->dtype()))) { - return tensorflow::errors::InvalidArgument( - "Can't copy Tensor with type ", - tensorflow::DataTypeString(src->dtype()), " to device ", - DeviceName(dstd), "."); - } - tensorflow::AllocatorAttributes attr; - if (src->dtype() == tensorflow::DT_VARIANT) { - attr.set_on_host(true); - } - tensorflow::Tensor dst(dstd->GetAllocator(attr), src->dtype(), src->shape()); - if (src->shape().num_elements() == 0) { - dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(dst, dstd, dstd); - return tensorflow::Status::OK(); - } - tensorflow::DeviceContext* src_device_context = nullptr; - if (!src_cpu) { - src_device_context = srcd->tensorflow_gpu_device_info()->default_context; - } - tensorflow::DeviceContext* dst_device_context = nullptr; - if (!dst_cpu) { - dst_device_context = dstd->tensorflow_gpu_device_info()->default_context; - } - // TODO(ashankar): The Sync() call below may be more aggressive than - // necessary. It is based on knowledge of implementation details - that - // GPU devices are implemented using 3 streams - one for host->device copies, - // one for device->host copies and one for sending operations to the GPU. - // With that setup, Sync()ing across all 3 streams should be sufficient - // but more than necessary (since it waits for operations that might have - // nothing to do with this tensor to complete). - TF_RETURN_IF_ERROR(srcd->Sync()); - tensorflow::Notification n; - tensorflow::Status status; - tensorflow::CopyTensor::ViaDMA("copy", src_device_context, dst_device_context, - srcd, dstd, tensorflow::AllocatorAttributes(), - tensorflow::AllocatorAttributes(), src, &dst, - [&status, &n](const tensorflow::Status& s) { - status = s; - n.Notify(); - }); - n.WaitForNotification(); - if (status.ok()) { - dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(dst, dstd, dstd); - } - return status; -} -} // namespace - extern "C" { TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, @@ -509,49 +434,6 @@ void TFE_OpSetAttrFunctionList(TFE_Op* op, const char* attr_name, namespace { -class CopyToDeviceNode : public tensorflow::EagerNode { - public: - CopyToDeviceNode(tensorflow::TensorHandle* src, tensorflow::Device* dstd, - TFE_Context* ctx) - : tensorflow::EagerNode(ctx->context.NextId()), - src_(src), - dstd_(dstd), - ctx_(ctx), - dst_(new tensorflow::TensorHandle(id, src_->dtype, &ctx->context)) { - src_->Ref(); - dst_->Ref(); - } - - ~CopyToDeviceNode() override { - src_->Unref(); - dst_->Unref(); - } - - tensorflow::Status Run() override { - tensorflow::TensorHandle* temp = nullptr; - TF_RETURN_IF_ERROR(TensorHandleCopyToDevice(src_, ctx_, dstd_, &temp)); - const tensorflow::Tensor* tensor = nullptr; - tensorflow::Device* device = nullptr; - tensorflow::Device* op_device = nullptr; - tensorflow::Status status = - temp->TensorAndDevice(&tensor, &device, &op_device); - // `temp` is a ready handle. So the following call should return OK. - TF_DCHECK_OK(status) << status.error_message(); - DCHECK(tensor); - dst_->SetTensorAndDevice(*tensor, device, op_device); - temp->Unref(); - return tensorflow::Status::OK(); - } - - tensorflow::TensorHandle* dst() { return dst_; } - - private: - tensorflow::TensorHandle* src_; - tensorflow::Device* dstd_; - TFE_Context* ctx_; - tensorflow::TensorHandle* dst_; -}; - // TODO(apassos) move to TensorHandle tensorflow::TensorHandle* TFE_TensorHandleCopyToDevice_Internal( tensorflow::TensorHandle* h, TFE_Context* ctx, const char* device_name, @@ -569,7 +451,8 @@ tensorflow::TensorHandle* TFE_TensorHandleCopyToDevice_Internal( if (ctx->context.Async()) { // Note that `h` may not be currently ready. However execution order will // make sure that `h` is ready before the copy is actually done. - CopyToDeviceNode* node = new CopyToDeviceNode(h, dstd, ctx); + tensorflow::CopyToDeviceNode* node = + new tensorflow::CopyToDeviceNode(h, dstd, &ctx->context); tensorflow::TensorHandle* output = node->dst(); // Note that calling Add makes `node` accessible by the EagerExecutor // thread. So further accesses need to be thread-safe. @@ -577,7 +460,7 @@ tensorflow::TensorHandle* TFE_TensorHandleCopyToDevice_Internal( return output; } else { tensorflow::TensorHandle* output = nullptr; - status->status = TensorHandleCopyToDevice(h, ctx, dstd, &output); + status->status = h->CopyToDevice(&ctx->context, dstd, &output); return output; } } diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index 02fb83200a..a619cac9a4 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -77,6 +77,26 @@ tf_cuda_library( ], ) +tf_cuda_library( + name = "copy_to_device_node", + hdrs = [ + "copy_to_device_node.h", + ], + visibility = ["//tensorflow:internal"], + deps = [ + ":context", + ":eager_executor", + ":tensor_handle", + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:session_options", + ], +) + tf_cuda_library( name = "kernel_and_device", srcs = [ diff --git a/tensorflow/core/common_runtime/eager/copy_to_device_node.h b/tensorflow/core/common_runtime/eager/copy_to_device_node.h new file mode 100644 index 0000000000..8a887540b0 --- /dev/null +++ b/tensorflow/core/common_runtime/eager/copy_to_device_node.h @@ -0,0 +1,69 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_COPY_TO_DEVICE_NODE_H_ +#define TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_COPY_TO_DEVICE_NODE_H_ + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/eager/eager_executor.h" +#include "tensorflow/core/common_runtime/eager/tensor_handle.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/lib/core/status.h" + +namespace tensorflow { + +class CopyToDeviceNode : public EagerNode { + public: + CopyToDeviceNode(TensorHandle* src, Device* dstd, EagerContext* ctx) + : EagerNode(ctx->NextId()), + src_(src), + dstd_(dstd), + ctx_(ctx), + dst_(new TensorHandle(id, src_->dtype, ctx)) { + src_->Ref(); + dst_->Ref(); + } + + ~CopyToDeviceNode() override { + src_->Unref(); + dst_->Unref(); + } + + Status Run() override { + TensorHandle* temp = nullptr; + TF_RETURN_IF_ERROR(src_->CopyToDevice(ctx_, dstd_, &temp)); + const Tensor* tensor = nullptr; + Device* device = nullptr; + Device* op_device = nullptr; + Status status = temp->TensorAndDevice(&tensor, &device, &op_device); + // `temp` is a ready handle. So the following call should return OK. + TF_DCHECK_OK(status) << status.error_message(); + DCHECK(tensor); + dst_->SetTensorAndDevice(*tensor, device, op_device); + temp->Unref(); + return Status::OK(); + } + + TensorHandle* dst() { return dst_; } + + private: + TensorHandle* src_; + Device* dstd_; + EagerContext* ctx_; + TensorHandle* dst_; +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_COMMON_RUNTIME_EAGER_COPY_TO_DEVICE_NODE_H_ diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.cc b/tensorflow/core/common_runtime/eager/tensor_handle.cc index 5bc1700627..328cd5dd5c 100644 --- a/tensorflow/core/common_runtime/eager/tensor_handle.cc +++ b/tensorflow/core/common_runtime/eager/tensor_handle.cc @@ -22,6 +22,7 @@ limitations under the License. #include #include +#include "tensorflow/core/common_runtime/copy_tensor.h" #include "tensorflow/core/common_runtime/device.h" #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/eager/context.h" @@ -104,4 +105,74 @@ void TensorHandle::SetTensorAndDevice(const tensorflow::Tensor& tensor, op_device_ = op_device; } +Status TensorHandle::CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, + TensorHandle** output) { + const tensorflow::Tensor* src = nullptr; + tensorflow::Device* srcd = nullptr; + // TODO(agarwal): src_opd is unused. Perhaps allow TensorAndDevice to accept + // nullptr. + tensorflow::Device* src_opd = nullptr; + TF_RETURN_IF_ERROR(TensorAndDevice(&src, &srcd, &src_opd)); + if (srcd == nullptr) srcd = ctx->HostCPU(); + bool is_same_device = (srcd == dstd) || (srcd->name() == dstd->name()); + const bool dst_cpu = dstd->tensorflow_gpu_device_info() == nullptr; + const bool src_cpu = srcd->tensorflow_gpu_device_info() == nullptr; + // both_on_cpu can be true and yet is_same_device is false, if one of src/dst + // has device type XLA_CPU, and the other CPU. + const bool both_on_cpu = src_cpu && dst_cpu; + if (is_same_device || both_on_cpu) { + dstd = dst_cpu ? nullptr : dstd; + *output = new tensorflow::TensorHandle(*src, dstd, dstd); + return tensorflow::Status::OK(); + } + if (!dst_cpu && (src->dtype() != tensorflow::DT_VARIANT && + !tensorflow::DataTypeCanUseMemcpy(src->dtype()))) { + return tensorflow::errors::InvalidArgument( + "Can't copy Tensor with type ", + tensorflow::DataTypeString(src->dtype()), " to device ", dstd->name(), + "."); + } + tensorflow::AllocatorAttributes attr; + if (src->dtype() == tensorflow::DT_VARIANT) { + attr.set_on_host(true); + } + tensorflow::Tensor dst(dstd->GetAllocator(attr), src->dtype(), src->shape()); + if (src->shape().num_elements() == 0) { + dstd = dst_cpu ? nullptr : dstd; + *output = new tensorflow::TensorHandle(dst, dstd, dstd); + return tensorflow::Status::OK(); + } + tensorflow::DeviceContext* src_device_context = nullptr; + if (!src_cpu) { + src_device_context = srcd->tensorflow_gpu_device_info()->default_context; + } + tensorflow::DeviceContext* dst_device_context = nullptr; + if (!dst_cpu) { + dst_device_context = dstd->tensorflow_gpu_device_info()->default_context; + } + // TODO(ashankar): The Sync() call below may be more aggressive than + // necessary. It is based on knowledge of implementation details - that + // GPU devices are implemented using 3 streams - one for host->device copies, + // one for device->host copies and one for sending operations to the GPU. + // With that setup, Sync()ing across all 3 streams should be sufficient + // but more than necessary (since it waits for operations that might have + // nothing to do with this tensor to complete). + TF_RETURN_IF_ERROR(srcd->Sync()); + tensorflow::Notification n; + tensorflow::Status status; + tensorflow::CopyTensor::ViaDMA("copy", src_device_context, dst_device_context, + srcd, dstd, tensorflow::AllocatorAttributes(), + tensorflow::AllocatorAttributes(), src, &dst, + [&status, &n](const tensorflow::Status& s) { + status = s; + n.Notify(); + }); + n.WaitForNotification(); + if (status.ok()) { + dstd = dst_cpu ? nullptr : dstd; + *output = new tensorflow::TensorHandle(dst, dstd, dstd); + } + return status; +} + } // namespace tensorflow diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.h b/tensorflow/core/common_runtime/eager/tensor_handle.h index 97e67e4652..eb69a13c06 100644 --- a/tensorflow/core/common_runtime/eager/tensor_handle.h +++ b/tensorflow/core/common_runtime/eager/tensor_handle.h @@ -85,6 +85,9 @@ class TensorHandle : public core::RefCounted { tensorflow::Device* device, tensorflow::Device* op_device); + Status CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, + TensorHandle** output); + // dtype for the handle. It must be the same as t.dtype() once the handle is // ready. const DataType dtype; -- GitLab From 97249979d9a76ae05d590f9cbe199c0b47712b4f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 16:16:22 -0700 Subject: [PATCH 182/906] bug fix: evaluate nodes before swap the original graph PiperOrigin-RevId: 190291844 --- tensorflow/core/grappler/optimizers/constant_folding_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index 914a9257ee..6340565bcd 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -1922,6 +1922,8 @@ TEST_F(ConstantFoldingTest, PartialFolding_Concat) { item.fetch = {"concat0", "concat1", "concat2", "concat3", "concat4", "concat5", "concat6", "concat7", "concat8", "concat9"}; + auto tensors_expected = EvaluateNodes(item.graph, {"concat0"}); + EXPECT_EQ(1, tensors_expected.size()); ConstantFolding optimizer(nullptr /* cpu_device */); GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); @@ -1971,9 +1973,7 @@ TEST_F(ConstantFoldingTest, PartialFolding_Concat) { } } - auto tensors_expected = EvaluateNodes(item.graph, {"concat0"}); auto tensors = EvaluateNodes(output, {"concat0"}); - EXPECT_EQ(1, tensors_expected.size()); EXPECT_EQ(1, tensors.size()); test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); } -- GitLab From 202e4f3b3699e8e40e478402462f76ae853fecbf Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Fri, 23 Mar 2018 16:28:16 -0700 Subject: [PATCH 183/906] Make _USE_C_API = True and _USE_C_SHAPES = False work with handle data. This change makes _set_shapes_for_outputs_c_api fetch and set Tensor._handle_data. This is necessary for running the Python shape inference code on resource tensors. PiperOrigin-RevId: 190293303 --- tensorflow/c/BUILD | 2 ++ tensorflow/c/python_api.cc | 26 +++++++++++++++ tensorflow/c/python_api.h | 7 ++++ tensorflow/python/BUILD | 2 ++ tensorflow/python/client/tf_session.i | 1 + tensorflow/python/framework/importer_test.py | 34 ++++++++++++++++++++ tensorflow/python/framework/ops.py | 9 ++++++ 7 files changed, 81 insertions(+) diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index d096647558..f4a486d330 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -279,6 +279,8 @@ tf_cuda_library( deps = [ ":c_api", ":c_api_internal", + # TODO(b/74620627): remove when _USE_C_SHAPES is removed + "//tensorflow/python:cpp_shape_inference_proto_cc", ], ) diff --git a/tensorflow/c/python_api.cc b/tensorflow/c/python_api.cc index cd604538f1..93155998b8 100644 --- a/tensorflow/c/python_api.cc +++ b/tensorflow/c/python_api.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/c/python_api.h" #include "tensorflow/c/c_api_internal.h" +#include "tensorflow/python/framework/cpp_shape_inference.pb.h" namespace tensorflow { @@ -109,4 +110,29 @@ void ExtendSession(TF_Session* session, TF_Status* status) { session->extend_before_run = false; } +std::string ResourceHandleShapeAndType(TF_Graph* graph, TF_Output output) { + Node* node = &output.oper->node; + CppShapeInferenceResult::HandleData handle_data; + handle_data.set_is_set(true); + { + mutex_lock l(graph->mu); + tensorflow::shape_inference::InferenceContext* ic = + graph->refiner.GetContext(node); + CHECK(ic != nullptr); + CHECK_LT(output.index, ic->num_outputs()); + const auto* shapes_and_types = + ic->output_handle_shapes_and_types(output.index); + if (shapes_and_types == nullptr) return ""; + + for (const auto& p : *shapes_and_types) { + auto* out_shape_and_type = handle_data.add_shape_and_type(); + ic->ShapeHandleToProto(p.shape, out_shape_and_type->mutable_shape()); + out_shape_and_type->set_dtype(p.dtype); + } + } + string result; + handle_data.SerializeToString(&result); + return result; +} + } // namespace tensorflow diff --git a/tensorflow/c/python_api.h b/tensorflow/c/python_api.h index 13b680b3a2..2d4c8cd9ed 100644 --- a/tensorflow/c/python_api.h +++ b/tensorflow/c/python_api.h @@ -16,6 +16,8 @@ limitations under the License. #ifndef TENSORFLOW_C_PYTHON_API_H_ #define TENSORFLOW_C_PYTHON_API_H_ +#include + #include "tensorflow/c/c_api.h" // These functions can be removed without notice. They exist to facilitate some @@ -51,6 +53,11 @@ void SetRequireShapeInferenceFns(TF_Graph* graph, bool require); // the graph after the session has been made aware of them. void ExtendSession(TF_Session* session, TF_Status* status); +// Returns the serialized CppShapeInferenceResult::HandleData proto for +// `output` if its a resource tensor, or otherwise returns the empty string. +// TODO(b/74620627): remove when _USE_C_SHAPES is removed +std::string ResourceHandleShapeAndType(TF_Graph* graph, TF_Output output); + } // namespace tensorflow #endif // TENSORFLOW_C_PYTHON_API_H_ diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 0e2b980213..acfdcd15f7 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3128,6 +3128,8 @@ tf_proto_library( srcs = ["framework/cpp_shape_inference.proto"], cc_api_version = 2, protodeps = tf_additional_all_protos(), + # TODO(b/74620627): remove when _USE_C_SHAPES is removed + visibility = ["//tensorflow:internal"], ) py_test( diff --git a/tensorflow/python/client/tf_session.i b/tensorflow/python/client/tf_session.i index e88fc0c01a..70a3d032f4 100644 --- a/tensorflow/python/client/tf_session.i +++ b/tensorflow/python/client/tf_session.i @@ -723,6 +723,7 @@ def TF_Reset(target, containers=None, config=None): %unignore TF_TryEvaluateConstant_wrapper; %noexception TF_TryEvaluateConstant_wrapper; %unignore ExtendSession; +%unignore ResourceHandleShapeAndType; %include "tensorflow/python/client/tf_session_helper.h" diff --git a/tensorflow/python/framework/importer_test.py b/tensorflow/python/framework/importer_test.py index 6593b17184..369669c2e6 100644 --- a/tensorflow/python/framework/importer_test.py +++ b/tensorflow/python/framework/importer_test.py @@ -39,6 +39,7 @@ from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn_ops from tensorflow.python.ops import random_ops +from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variables import tensorflow.python.ops.nn_grad # pylint: disable=unused-import from tensorflow.python.platform import test @@ -356,6 +357,39 @@ class ImportGraphDefTest(test.TestCase): self.assertEqual(d._input_types, [dtypes.int32_ref, dtypes.int32]) self.assertEqual(d.outputs, []) + def testResources(self): + # Produce GraphDef containing a ops producing and consuming resources. + graph = ops.Graph() + with graph.as_default(): + var = resource_variable_ops.ResourceVariable(1.0) + var_assign = var.assign(2.0) + # Use an op that requires handle shape to be set. + var_shape = resource_variable_ops.variable_shape(var.handle) + init = variables.global_variables_initializer() + graph_def = graph.as_graph_def() + + # Import the GraphDef. + with ops.Graph().as_default(): + # pylint: disable=unused-variable + imported_var, imported_assign, imported_shape, imported_init = ( + importer.import_graph_def( + graph_def, + return_elements=[var.name, var_assign.name, var_shape.name, + init.name])) + + # Make sure the handle shape is set on the imported variable. + new_var_shape = resource_variable_ops.variable_shape(imported_var) + # pylint: enable=unused-variable + + # Run the imported graph. + # TODO(b/76173421): make this work (currently DCHECKS) + # with self.test_session() as sess: + # sess.run(imported_init) + # self.assertEqual(sess.run(imported_var), 1.0) + # self.assertEqual(sess.run(imported_assign), 2.0) + # self.assertEqual(list(sess.run(imported_shape)), []) + # self.assertEqual(list(sess.run(new_var_shape)), []) + def testWhileLoop(self): # Produce GraphDef containing while loop. graph = ops.Graph() diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 93edaa0cf0..1fa9285e43 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -42,6 +42,7 @@ from tensorflow.python.eager import context from tensorflow.python.eager import core from tensorflow.python.eager import tape from tensorflow.python.framework import c_api_util +from tensorflow.python.framework import cpp_shape_inference_pb2 from tensorflow.python.framework import device as pydev from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors @@ -295,6 +296,7 @@ class Tensor(_TensorLike): # Attributes used for C++ shape inference. Not inspected, only forwarded. # If set, will be a HandleData object from cpp_shape_inference.proto. + # TODO(b/74620627): remove when _USE_C_SHAPES is removed self._handle_data = None self._id = uid() @@ -2472,6 +2474,13 @@ def _set_shapes_for_outputs_c_api(op): shape_vector = [None if d == -1 else d for d in shape_vector] output.set_shape(tensor_shape.TensorShape(shape_vector)) + serialized = c_api.ResourceHandleShapeAndType(op._graph._c_graph, + output._as_tf_output()) + if serialized: + output._handle_data = (cpp_shape_inference_pb2.CppShapeInferenceResult + .HandleData.FromString(serialized.encode())) + else: + output._handle_data = None # TODO(skyewm): remove this when _USE_C_API flag is removed. def _set_shapes_for_outputs(op): -- GitLab From 6e523342d57b175e698bb8379979104e3e0335ac Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 17:19:10 -0700 Subject: [PATCH 184/906] Update ops-related pbtxt files. PiperOrigin-RevId: 190299240 --- tensorflow/contrib/estimator/BUILD | 2 +- .../core/ops/compat/ops_history.v1.pbtxt | 359 ++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 359 ++++++++++++++++++ 3 files changed, 719 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index 24374266dc..c846343d6d 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -358,7 +358,7 @@ cuda_py_test( size = "medium", srcs = ["python/estimator/replicate_model_fn_test.py"], additional_deps = [ - "//third_party/py/absl/testing:parameterized", + "@absl_py//absl/testing:parameterized", "//tensorflow/python/estimator", "//tensorflow/python/estimator:dnn", "//tensorflow/python/estimator:export_export", diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index b41826d6eb..05d6e02281 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -43705,6 +43705,210 @@ op { } is_stateful: true } +op { + name: "ResourceScatterDiv" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMax" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMin" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMul" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "ResourceScatterNdUpdate" input_arg { @@ -43742,6 +43946,57 @@ op { } is_stateful: true } +op { + name: "ResourceScatterSub" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "ResourceScatterUpdate" input_arg { @@ -48901,6 +49156,110 @@ op { } } } +op { + name: "ScatterMax" + input_arg { + name: "ref" + type_attr: "T" + is_ref: true + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "T" + } + output_arg { + name: "output_ref" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: false + } + } +} +op { + name: "ScatterMin" + input_arg { + name: "ref" + type_attr: "T" + is_ref: true + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "T" + } + output_arg { + name: "output_ref" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: false + } + } +} op { name: "ScatterMul" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index af2c563489..274a7fbf75 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -21658,6 +21658,210 @@ op { } is_stateful: true } +op { + name: "ResourceScatterDiv" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMax" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMin" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} +op { + name: "ResourceScatterMul" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "ResourceScatterNdUpdate" input_arg { @@ -21695,6 +21899,57 @@ op { } is_stateful: true } +op { + name: "ResourceScatterSub" + input_arg { + name: "resource" + type: DT_RESOURCE + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "dtype" + } + attr { + name: "dtype" + type: "type" + allowed_values { + list { + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_UINT8 + type: DT_INT16 + type: DT_INT8 + type: DT_COMPLEX64 + type: DT_INT64 + type: DT_QINT8 + type: DT_QUINT8 + type: DT_QINT32 + type: DT_BFLOAT16 + type: DT_UINT16 + type: DT_COMPLEX128 + type: DT_HALF + type: DT_UINT32 + type: DT_UINT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + is_stateful: true +} op { name: "ResourceScatterUpdate" input_arg { @@ -23434,6 +23689,110 @@ op { } } } +op { + name: "ScatterMax" + input_arg { + name: "ref" + type_attr: "T" + is_ref: true + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "T" + } + output_arg { + name: "output_ref" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: false + } + } +} +op { + name: "ScatterMin" + input_arg { + name: "ref" + type_attr: "T" + is_ref: true + } + input_arg { + name: "indices" + type_attr: "Tindices" + } + input_arg { + name: "updates" + type_attr: "T" + } + output_arg { + name: "output_ref" + type_attr: "T" + is_ref: true + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_HALF + type: DT_BFLOAT16 + type: DT_FLOAT + type: DT_DOUBLE + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "Tindices" + type: "type" + allowed_values { + list { + type: DT_INT32 + type: DT_INT64 + } + } + } + attr { + name: "use_locking" + type: "bool" + default_value { + b: false + } + } +} op { name: "ScatterMul" input_arg { -- GitLab From d40c53dd2cb7c0e3ec20ca56f5c3c95038820900 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Fri, 23 Mar 2018 17:28:10 -0700 Subject: [PATCH 185/906] Set the stream in TransformTensor. PiperOrigin-RevId: 190300166 --- tensorflow/stream_executor/cuda/cuda_dnn.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index 03e3e0857f..ab5e6590e0 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -3157,12 +3157,18 @@ bool CudnnSupport::DoTransformTensor(Stream* stream, dnn::DataType output_type, float scale, DeviceMemoryBase* output_data) { mutex_lock lock{dnn_handle_mutex_}; + cudnnStatus_t status = wrap::cudnnSetStream(parent_, ToHandle(dnn_handle_), + AsCUDAStreamValue(stream)); + if (status != CUDNN_STATUS_SUCCESS) { + LOG(FATAL) << "failed to set stream for cudnn handle: " << ToString(status); + } + float beta = 0.0f; ScopedTensorDescriptor input_tensor_desc( parent_, input_desc, ToCudnnDataType(input_type, input_desc.layout())); ScopedTensorDescriptor output_tensor_desc( parent_, output_desc, ToCudnnDataType(output_type, output_desc.layout())); - cudnnStatus_t status = wrap::cudnnTransformTensor( + status = wrap::cudnnTransformTensor( parent_, ToHandle(dnn_handle_), &scale, input_tensor_desc.handle(), input_data.opaque(), &beta, output_tensor_desc.handle(), output_data->opaque()); -- GitLab From c275f2dffb7423328428553f2aafe3b011b48372 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 17:49:47 -0700 Subject: [PATCH 186/906] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 190302194 --- tensorflow/go/op/wrappers.go | 3410 +++++++++++++++++----------------- 1 file changed, 1705 insertions(+), 1705 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 5ddd32ed48..838f4f2301 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -1089,184 +1089,190 @@ func ExpandDims(scope *Scope, input tf.Output, axis tf.Output) (output tf.Output return op.Output(0) } -// Returns (x - y)(x - y) element-wise. +// A placeholder op that passes through `input` when its output is not fed. // -// *NOTE*: `SquaredDifference` supports broadcasting. More about broadcasting -// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) -func SquaredDifference(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { +// Arguments: +// input: The default value to produce when `output` is not fed. +// shape: The (possibly partial) shape of the tensor. +// +// Returns A placeholder tensor that defaults to `input` if it is not fed. +func PlaceholderWithDefault(scope *Scope, input tf.Output, shape tf.Shape) (output tf.Output) { if scope.Err() != nil { return } + attrs := map[string]interface{}{"shape": shape} opspec := tf.OpSpec{ - Type: "SquaredDifference", + Type: "PlaceholderWithDefault", Input: []tf.Input{ - x, y, + input, }, + Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) } -// Forwards the input to the output. +// A placeholder op for a value that will be fed into the computation. // -// This operator represents the loop termination condition used by the -// "pivot" switches of a loop. +// DEPRECATED at GraphDef version 23: Placeholder now behaves the same as PlaceholderV2. +// +// N.B. This operation will fail with an error if it is executed. It is +// intended as a way to represent a value that will always be fed, and to +// provide attrs that enable the fed value to be checked at runtime. // // Arguments: -// input: A boolean scalar, representing the branch predicate of the Switch op. +// dtype: The type of elements in the tensor. +// shape: The shape of the tensor. The shape can be any partially-specified +// shape. To be unconstrained, pass in a shape with unknown rank. // -// Returns The same tensor as `input`. -func LoopCond(scope *Scope, input tf.Output) (output tf.Output) { +// Returns A placeholder tensor that must be replaced using the feed mechanism. +func PlaceholderV2(scope *Scope, dtype tf.DataType, shape tf.Shape) (output tf.Output) { if scope.Err() != nil { return } + attrs := map[string]interface{}{"dtype": dtype, "shape": shape} opspec := tf.OpSpec{ - Type: "LoopCond", - Input: []tf.Input{ - input, - }, + Type: "PlaceholderV2", + + Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) } -// QuantizedMulAttr is an optional argument to QuantizedMul. -type QuantizedMulAttr func(optionalAttr) +// PlaceholderAttr is an optional argument to Placeholder. +type PlaceholderAttr func(optionalAttr) -// QuantizedMulToutput sets the optional Toutput attribute to value. -// If not specified, defaults to DT_QINT32 -func QuantizedMulToutput(value tf.DataType) QuantizedMulAttr { +// PlaceholderShape sets the optional shape attribute to value. +// +// value: (Optional) The shape of the tensor. If the shape has 0 dimensions, the +// shape is unconstrained. +// If not specified, defaults to +func PlaceholderShape(value tf.Shape) PlaceholderAttr { return func(m optionalAttr) { - m["Toutput"] = value + m["shape"] = value } } -// Returns x * y element-wise, working on quantized buffers. -// -// Arguments: -// +// A placeholder op for a value that will be fed into the computation. // -// min_x: The float value that the lowest quantized `x` value represents. -// max_x: The float value that the highest quantized `x` value represents. -// min_y: The float value that the lowest quantized `y` value represents. -// max_y: The float value that the highest quantized `y` value represents. +// N.B. This operation will fail with an error if it is executed. It is +// intended as a way to represent a value that will always be fed, and to +// provide attrs that enable the fed value to be checked at runtime. // -// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. +// Arguments: +// dtype: The type of elements in the tensor. // -// *NOTE*: `QuantizedMul` supports limited forms of broadcasting. More about -// broadcasting [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) -func QuantizedMul(scope *Scope, x tf.Output, y tf.Output, min_x tf.Output, max_x tf.Output, min_y tf.Output, max_y tf.Output, optional ...QuantizedMulAttr) (z tf.Output, min_z tf.Output, max_z tf.Output) { +// Returns A placeholder tensor that must be replaced using the feed mechanism. +func Placeholder(scope *Scope, dtype tf.DataType, optional ...PlaceholderAttr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{} + attrs := map[string]interface{}{"dtype": dtype} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "QuantizedMul", - Input: []tf.Input{ - x, y, min_x, max_x, min_y, max_y, - }, + Type: "Placeholder", + Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) -} - -// QuantizedMatMulAttr is an optional argument to QuantizedMatMul. -type QuantizedMatMulAttr func(optionalAttr) - -// QuantizedMatMulToutput sets the optional Toutput attribute to value. -// If not specified, defaults to DT_QINT32 -func QuantizedMatMulToutput(value tf.DataType) QuantizedMatMulAttr { - return func(m optionalAttr) { - m["Toutput"] = value - } + return op.Output(0) } -// QuantizedMatMulTransposeA sets the optional transpose_a attribute to value. +// Gradient op for `MirrorPad` op. This op folds a mirror-padded tensor. // -// value: If true, `a` is transposed before multiplication. -// If not specified, defaults to false -func QuantizedMatMulTransposeA(value bool) QuantizedMatMulAttr { - return func(m optionalAttr) { - m["transpose_a"] = value - } -} - -// QuantizedMatMulTransposeB sets the optional transpose_b attribute to value. +// This operation folds the padded areas of `input` by `MirrorPad` according to the +// `paddings` you specify. `paddings` must be the same as `paddings` argument +// given to the corresponding `MirrorPad` op. // -// value: If true, `b` is transposed before multiplication. -// If not specified, defaults to false -func QuantizedMatMulTransposeB(value bool) QuantizedMatMulAttr { - return func(m optionalAttr) { - m["transpose_b"] = value - } -} - -// QuantizedMatMulTactivation sets the optional Tactivation attribute to value. +// The folded size of each dimension D of the output is: // -// value: The type of output produced by activation function -// following this operation. -// If not specified, defaults to DT_QUINT8 -func QuantizedMatMulTactivation(value tf.DataType) QuantizedMatMulAttr { - return func(m optionalAttr) { - m["Tactivation"] = value - } -} - -// Perform a quantized matrix multiplication of `a` by the matrix `b`. +// `input.dim_size(D) - paddings(D, 0) - paddings(D, 1)` // -// The inputs must be two-dimensional matrices and the inner dimension of -// `a` (after being transposed if `transpose_a` is non-zero) must match the -// outer dimension of `b` (after being transposed if `transposed_b` is -// non-zero). +// For example: +// +// ``` +// # 't' is [[1, 2, 3], [4, 5, 6], [7, 8, 9]]. +// # 'paddings' is [[0, 1]], [0, 1]]. +// # 'mode' is SYMMETRIC. +// # rank of 't' is 2. +// pad(t, paddings) ==> [[ 1, 5] +// [11, 28]] +// ``` // // Arguments: -// a: Must be a two-dimensional tensor. -// b: Must be a two-dimensional tensor. -// min_a: The float value that the lowest quantized `a` value represents. -// max_a: The float value that the highest quantized `a` value represents. -// min_b: The float value that the lowest quantized `b` value represents. -// max_b: The float value that the highest quantized `b` value represents. +// input: The input tensor to be folded. +// paddings: A two-column matrix specifying the padding sizes. The number of +// rows must be the same as the rank of `input`. +// mode: The mode used in the `MirrorPad` op. // -// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. -func QuantizedMatMul(scope *Scope, a tf.Output, b tf.Output, min_a tf.Output, max_a tf.Output, min_b tf.Output, max_b tf.Output, optional ...QuantizedMatMulAttr) (out tf.Output, min_out tf.Output, max_out tf.Output) { +// Returns The folded tensor. +func MirrorPadGrad(scope *Scope, input tf.Output, paddings tf.Output, mode string) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } + attrs := map[string]interface{}{"mode": mode} opspec := tf.OpSpec{ - Type: "QuantizedMatMul", + Type: "MirrorPadGrad", Input: []tf.Input{ - a, b, min_a, max_a, min_b, max_b, + input, paddings, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) + return op.Output(0) } -// A placeholder op that passes through `input` when its output is not fed. +// Pads a tensor with mirrored values. +// +// This operation pads a `input` with mirrored values according to the `paddings` +// you specify. `paddings` is an integer tensor with shape `[n, 2]`, where n is +// the rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates +// how many values to add before the contents of `input` in that dimension, and +// `paddings[D, 1]` indicates how many values to add after the contents of `input` +// in that dimension. Both `paddings[D, 0]` and `paddings[D, 1]` must be no greater +// than `input.dim_size(D)` (or `input.dim_size(D) - 1`) if `copy_border` is true +// (if false, respectively). +// +// The padded size of each dimension D of the output is: +// +// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` +// +// For example: +// +// ``` +// # 't' is [[1, 2, 3], [4, 5, 6]]. +// # 'paddings' is [[1, 1]], [2, 2]]. +// # 'mode' is SYMMETRIC. +// # rank of 't' is 2. +// pad(t, paddings) ==> [[2, 1, 1, 2, 3, 3, 2] +// [2, 1, 1, 2, 3, 3, 2] +// [5, 4, 4, 5, 6, 6, 5] +// [5, 4, 4, 5, 6, 6, 5]] +// ``` // // Arguments: -// input: The default value to produce when `output` is not fed. -// shape: The (possibly partial) shape of the tensor. +// input: The input tensor to be padded. +// paddings: A two-column matrix specifying the padding sizes. The number of +// rows must be the same as the rank of `input`. +// mode: Either `REFLECT` or `SYMMETRIC`. In reflect mode the padded regions +// do not include the borders, while in symmetric mode the padded regions +// do include the borders. For example, if `input` is `[1, 2, 3]` and `paddings` +// is `[0, 2]`, then the output is `[1, 2, 3, 2, 1]` in reflect mode, and +// it is `[1, 2, 3, 3, 2]` in symmetric mode. // -// Returns A placeholder tensor that defaults to `input` if it is not fed. -func PlaceholderWithDefault(scope *Scope, input tf.Output, shape tf.Shape) (output tf.Output) { +// Returns The padded tensor. +func MirrorPad(scope *Scope, input tf.Output, paddings tf.Output, mode string) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"shape": shape} + attrs := map[string]interface{}{"mode": mode} opspec := tf.OpSpec{ - Type: "PlaceholderWithDefault", + Type: "MirrorPad", Input: []tf.Input{ - input, + input, paddings, }, Attrs: attrs, } @@ -1274,38 +1280,78 @@ func PlaceholderWithDefault(scope *Scope, input tf.Output, shape tf.Shape) (outp return op.Output(0) } -// Returns the complex conjugate of a complex number. +// Pads a tensor. // -// Given a tensor `input` of complex numbers, this operation returns a tensor of -// complex numbers that are the complex conjugate of each element in `input`. The -// complex numbers in `input` must be of the form \\(a + bj\\), where *a* is the -// real part and *b* is the imaginary part. +// This operation pads `input` according to the `paddings` and `constant_values` +// you specify. `paddings` is an integer tensor with shape `[Dn, 2]`, where n is +// the rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates +// how many padding values to add before the contents of `input` in that dimension, +// and `paddings[D, 1]` indicates how many padding values to add after the contents +// of `input` in that dimension. `constant_values` is a scalar tensor of the same +// type as `input` that indicates the value to use for padding `input`. // -// The complex conjugate returned by this operation is of the form \\(a - bj\\). +// The padded size of each dimension D of the output is: +// +// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` // // For example: // // ``` -// # tensor 'input' is [-2.25 + 4.75j, 3.25 + 5.75j] -// tf.conj(input) ==> [-2.25 - 4.75j, 3.25 - 5.75j] +// # 't' is [[1, 1], [2, 2]] +// # 'paddings' is [[1, 1], [2, 2]] +// # 'constant_values' is 0 +// # rank of 't' is 2 +// pad(t, paddings) ==> [[0, 0, 0, 0, 0, 0] +// [0, 0, 1, 1, 0, 0] +// [0, 0, 2, 2, 0, 0] +// [0, 0, 0, 0, 0, 0]] // ``` -func Conj(scope *Scope, input tf.Output) (output tf.Output) { +func PadV2(scope *Scope, input tf.Output, paddings tf.Output, constant_values tf.Output) (output tf.Output) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "Conj", + Type: "PadV2", Input: []tf.Input{ - input, + input, paddings, constant_values, }, } op := scope.AddOperation(opspec) return op.Output(0) } -// ResourceSparseApplyMomentumAttr is an optional argument to ResourceSparseApplyMomentum. -type ResourceSparseApplyMomentumAttr func(optionalAttr) - +// Returns the complex conjugate of a complex number. +// +// Given a tensor `input` of complex numbers, this operation returns a tensor of +// complex numbers that are the complex conjugate of each element in `input`. The +// complex numbers in `input` must be of the form \\(a + bj\\), where *a* is the +// real part and *b* is the imaginary part. +// +// The complex conjugate returned by this operation is of the form \\(a - bj\\). +// +// For example: +// +// ``` +// # tensor 'input' is [-2.25 + 4.75j, 3.25 + 5.75j] +// tf.conj(input) ==> [-2.25 - 4.75j, 3.25 - 5.75j] +// ``` +func Conj(scope *Scope, input tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Conj", + Input: []tf.Input{ + input, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// ResourceSparseApplyMomentumAttr is an optional argument to ResourceSparseApplyMomentum. +type ResourceSparseApplyMomentumAttr func(optionalAttr) + // ResourceSparseApplyMomentumUseLocking sets the optional use_locking attribute to value. // // value: If `True`, updating of the var and accum tensors will be protected @@ -2063,6 +2109,47 @@ func LogUniformCandidateSampler(scope *Scope, true_classes tf.Output, num_true i return op.Output(0), op.Output(1), op.Output(2) } +// Returns (x - y)(x - y) element-wise. +// +// *NOTE*: `SquaredDifference` supports broadcasting. More about broadcasting +// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +func SquaredDifference(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "SquaredDifference", + Input: []tf.Input{ + x, y, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Forwards the input to the output. +// +// This operator represents the loop termination condition used by the +// "pivot" switches of a loop. +// +// Arguments: +// input: A boolean scalar, representing the branch predicate of the Switch op. +// +// Returns The same tensor as `input`. +func LoopCond(scope *Scope, input tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "LoopCond", + Input: []tf.Input{ + input, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // ApproximateEqualAttr is an optional argument to ApproximateEqual. type ApproximateEqualAttr func(optionalAttr) @@ -2391,50 +2478,6 @@ func Sign(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } -// QuantizedAddAttr is an optional argument to QuantizedAdd. -type QuantizedAddAttr func(optionalAttr) - -// QuantizedAddToutput sets the optional Toutput attribute to value. -// If not specified, defaults to DT_QINT32 -func QuantizedAddToutput(value tf.DataType) QuantizedAddAttr { - return func(m optionalAttr) { - m["Toutput"] = value - } -} - -// Returns x + y element-wise, working on quantized buffers. -// -// Arguments: -// -// -// min_x: The float value that the lowest quantized `x` value represents. -// max_x: The float value that the highest quantized `x` value represents. -// min_y: The float value that the lowest quantized `y` value represents. -// max_y: The float value that the highest quantized `y` value represents. -// -// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. -// -// *NOTE*: `QuantizedAdd` supports limited forms of broadcasting. More about -// broadcasting [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) -func QuantizedAdd(scope *Scope, x tf.Output, y tf.Output, min_x tf.Output, max_x tf.Output, min_y tf.Output, max_y tf.Output, optional ...QuantizedAddAttr) (z tf.Output, min_z tf.Output, max_z tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "QuantizedAdd", - Input: []tf.Input{ - x, y, min_x, max_x, min_y, max_y, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) -} - // ArgMinAttr is an optional argument to ArgMin. type ArgMinAttr func(optionalAttr) @@ -3741,32 +3784,6 @@ func MatrixDiag(scope *Scope, diagonal tf.Output) (output tf.Output) { return op.Output(0) } -// Given a quantized tensor described by (input, input_min, input_max), outputs a -// -// range that covers the actual values present in that tensor. This op is -// typically used to produce the requested_output_min and requested_output_max for -// Requantize. -// -// Arguments: -// -// input_min: The float value that the minimum quantized input value represents. -// input_max: The float value that the maximum quantized input value represents. -// -// Returns The computed min output.the computed max output. -func RequantizationRange(scope *Scope, input tf.Output, input_min tf.Output, input_max tf.Output) (output_min tf.Output, output_max tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "RequantizationRange", - Input: []tf.Input{ - input, input_min, input_max, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1) -} - // Returns the truth value of (x <= y) element-wise. // // *NOTE*: `LessEqual` supports broadcasting. More about broadcasting @@ -3943,46 +3960,6 @@ func BatchMatMul(scope *Scope, x tf.Output, y tf.Output, optional ...BatchMatMul return op.Output(0) } -// Pads a tensor. -// -// This operation pads `input` according to the `paddings` and `constant_values` -// you specify. `paddings` is an integer tensor with shape `[Dn, 2]`, where n is -// the rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates -// how many padding values to add before the contents of `input` in that dimension, -// and `paddings[D, 1]` indicates how many padding values to add after the contents -// of `input` in that dimension. `constant_values` is a scalar tensor of the same -// type as `input` that indicates the value to use for padding `input`. -// -// The padded size of each dimension D of the output is: -// -// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` -// -// For example: -// -// ``` -// # 't' is [[1, 1], [2, 2]] -// # 'paddings' is [[1, 1], [2, 2]] -// # 'constant_values' is 0 -// # rank of 't' is 2 -// pad(t, paddings) ==> [[0, 0, 0, 0, 0, 0] -// [0, 0, 1, 1, 0, 0] -// [0, 0, 2, 2, 0, 0] -// [0, 0, 0, 0, 0, 0]] -// ``` -func PadV2(scope *Scope, input tf.Output, paddings tf.Output, constant_values tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "PadV2", - Input: []tf.Input{ - input, paddings, constant_values, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Returns which elements of x are NaN. // // @compatibility(numpy) @@ -4292,52 +4269,6 @@ func MaxPoolGradGradV2(scope *Scope, orig_input tf.Output, orig_output tf.Output return op.Output(0) } -// MaxPoolAttr is an optional argument to MaxPool. -type MaxPoolAttr func(optionalAttr) - -// MaxPoolDataFormat sets the optional data_format attribute to value. -// -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the data is stored in the order of: -// [batch, in_height, in_width, in_channels]. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, in_channels, in_height, in_width]. -// If not specified, defaults to "NHWC" -func MaxPoolDataFormat(value string) MaxPoolAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// Performs max pooling on the input. -// -// Arguments: -// input: 4-D input to pool over. -// ksize: The size of the window for each dimension of the input tensor. -// strides: The stride of the sliding window for each dimension of the -// input tensor. -// padding: The type of padding algorithm to use. -// -// Returns The max pooled output tensor. -func MaxPool(scope *Scope, input tf.Output, ksize []int64, strides []int64, padding string, optional ...MaxPoolAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "MaxPool", - Input: []tf.Input{ - input, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Computes gradients of the maxpooling function. // // Arguments: @@ -5247,74 +5178,30 @@ func InvertPermutation(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } -// Gradient op for `MirrorPad` op. This op folds a mirror-padded tensor. +// BiasAddGradAttr is an optional argument to BiasAddGrad. +type BiasAddGradAttr func(optionalAttr) + +// BiasAddGradDataFormat sets the optional data_format attribute to value. // -// This operation folds the padded areas of `input` by `MirrorPad` according to the -// `paddings` you specify. `paddings` must be the same as `paddings` argument -// given to the corresponding `MirrorPad` op. +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the bias tensor will be added to the last dimension +// of the value tensor. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, in_channels, in_height, in_width]. +// The tensor will be added to "in_channels", the third-to-the-last +// dimension. +// If not specified, defaults to "NHWC" +func BiasAddGradDataFormat(value string) BiasAddGradAttr { + return func(m optionalAttr) { + m["data_format"] = value + } +} + +// The backward operation for "BiasAdd" on the "bias" tensor. // -// The folded size of each dimension D of the output is: -// -// `input.dim_size(D) - paddings(D, 0) - paddings(D, 1)` -// -// For example: -// -// ``` -// # 't' is [[1, 2, 3], [4, 5, 6], [7, 8, 9]]. -// # 'paddings' is [[0, 1]], [0, 1]]. -// # 'mode' is SYMMETRIC. -// # rank of 't' is 2. -// pad(t, paddings) ==> [[ 1, 5] -// [11, 28]] -// ``` -// -// Arguments: -// input: The input tensor to be folded. -// paddings: A two-column matrix specifying the padding sizes. The number of -// rows must be the same as the rank of `input`. -// mode: The mode used in the `MirrorPad` op. -// -// Returns The folded tensor. -func MirrorPadGrad(scope *Scope, input tf.Output, paddings tf.Output, mode string) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"mode": mode} - opspec := tf.OpSpec{ - Type: "MirrorPadGrad", - Input: []tf.Input{ - input, paddings, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// BiasAddGradAttr is an optional argument to BiasAddGrad. -type BiasAddGradAttr func(optionalAttr) - -// BiasAddGradDataFormat sets the optional data_format attribute to value. -// -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the bias tensor will be added to the last dimension -// of the value tensor. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, in_channels, in_height, in_width]. -// The tensor will be added to "in_channels", the third-to-the-last -// dimension. -// If not specified, defaults to "NHWC" -func BiasAddGradDataFormat(value string) BiasAddGradAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// The backward operation for "BiasAdd" on the "bias" tensor. -// -// It accumulates all the values from out_backprop into the feature dimension. -// For NHWC data format, the feature dimension is the last. For NCHW data format, -// the feature dimension is the third-to-last. +// It accumulates all the values from out_backprop into the feature dimension. +// For NHWC data format, the feature dimension is the last. For NCHW data format, +// the feature dimension is the third-to-last. // // Arguments: // out_backprop: Any number of dimensions. @@ -5411,297 +5298,220 @@ func FusedBatchNormV2(scope *Scope, x tf.Output, scale tf.Output, offset tf.Outp return op.Output(0), op.Output(1), op.Output(2), op.Output(3), op.Output(4) } -// AvgPoolGradAttr is an optional argument to AvgPoolGrad. -type AvgPoolGradAttr func(optionalAttr) - -// AvgPoolGradDataFormat sets the optional data_format attribute to value. +// Returns the rank of a tensor. // -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the data is stored in the order of: -// [batch, in_height, in_width, in_channels]. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, in_channels, in_height, in_width]. -// If not specified, defaults to "NHWC" -func AvgPoolGradDataFormat(value string) AvgPoolGradAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// Computes gradients of the average pooling function. +// This operation returns an integer representing the rank of `input`. // -// Arguments: -// orig_input_shape: 1-D. Shape of the original input to `avg_pool`. -// grad: 4-D with shape `[batch, height, width, channels]`. Gradients w.r.t. -// the output of `avg_pool`. -// ksize: The size of the sliding window for each dimension of the input. -// strides: The stride of the sliding window for each dimension of the input. -// padding: The type of padding algorithm to use. +// For example: // -// Returns 4-D. Gradients w.r.t. the input of `avg_pool`. -func AvgPoolGrad(scope *Scope, orig_input_shape tf.Output, grad tf.Output, ksize []int64, strides []int64, padding string, optional ...AvgPoolGradAttr) (output tf.Output) { +// ``` +// # 't' is [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]] +// # shape of tensor 't' is [2, 2, 3] +// rank(t) ==> 3 +// ``` +// +// **Note**: The rank of a tensor is not the same as the rank of a matrix. The rank +// of a tensor is the number of indices required to uniquely select each element +// of the tensor. Rank is also known as "order", "degree", or "ndims." +func Rank(scope *Scope, input tf.Output) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} - for _, a := range optional { - a(attrs) - } opspec := tf.OpSpec{ - Type: "AvgPoolGrad", + Type: "Rank", Input: []tf.Input{ - orig_input_shape, grad, + input, }, - Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) } -// StageClearAttr is an optional argument to StageClear. -type StageClearAttr func(optionalAttr) - -// StageClearCapacity sets the optional capacity attribute to value. -// If not specified, defaults to 0 -// -// REQUIRES: value >= 0 -func StageClearCapacity(value int64) StageClearAttr { - return func(m optionalAttr) { - m["capacity"] = value - } -} - -// StageClearMemoryLimit sets the optional memory_limit attribute to value. -// If not specified, defaults to 0 +// Transforms a Tensor into a serialized TensorProto proto. // -// REQUIRES: value >= 0 -func StageClearMemoryLimit(value int64) StageClearAttr { - return func(m optionalAttr) { - m["memory_limit"] = value - } -} - -// StageClearContainer sets the optional container attribute to value. -// If not specified, defaults to "" -func StageClearContainer(value string) StageClearAttr { - return func(m optionalAttr) { - m["container"] = value - } -} - -// StageClearSharedName sets the optional shared_name attribute to value. -// If not specified, defaults to "" -func StageClearSharedName(value string) StageClearAttr { - return func(m optionalAttr) { - m["shared_name"] = value - } -} - -// Op removes all elements in the underlying container. +// Arguments: +// tensor: A Tensor of type `T`. // -// Returns the created operation. -func StageClear(scope *Scope, dtypes []tf.DataType, optional ...StageClearAttr) (o *tf.Operation) { +// Returns A serialized TensorProto proto of the input tensor. +func SerializeTensor(scope *Scope, tensor tf.Output) (serialized tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtypes": dtypes} - for _, a := range optional { - a(attrs) - } opspec := tf.OpSpec{ - Type: "StageClear", - - Attrs: attrs, + Type: "SerializeTensor", + Input: []tf.Input{ + tensor, + }, } - return scope.AddOperation(opspec) + op := scope.AddOperation(opspec) + return op.Output(0) } -// ComputeAccidentalHitsAttr is an optional argument to ComputeAccidentalHits. -type ComputeAccidentalHitsAttr func(optionalAttr) - -// ComputeAccidentalHitsSeed sets the optional seed attribute to value. -// -// value: If either seed or seed2 are set to be non-zero, the random number -// generator is seeded by the given seed. Otherwise, it is seeded by a -// random seed. -// If not specified, defaults to 0 -func ComputeAccidentalHitsSeed(value int64) ComputeAccidentalHitsAttr { - return func(m optionalAttr) { - m["seed"] = value - } -} +// MatrixSolveAttr is an optional argument to MatrixSolve. +type MatrixSolveAttr func(optionalAttr) -// ComputeAccidentalHitsSeed2 sets the optional seed2 attribute to value. +// MatrixSolveAdjoint sets the optional adjoint attribute to value. // -// value: An second seed to avoid seed collision. -// If not specified, defaults to 0 -func ComputeAccidentalHitsSeed2(value int64) ComputeAccidentalHitsAttr { +// value: Boolean indicating whether to solve with `matrix` or its (block-wise) +// adjoint. +// If not specified, defaults to false +func MatrixSolveAdjoint(value bool) MatrixSolveAttr { return func(m optionalAttr) { - m["seed2"] = value + m["adjoint"] = value } } -// Computes the ids of the positions in sampled_candidates that match true_labels. +// Solves systems of linear equations. // -// When doing log-odds NCE, the result of this op should be passed through a -// SparseToDense op, then added to the logits of the sampled candidates. This has -// the effect of 'removing' the sampled labels that match the true labels by -// making the classifier sure that they are sampled labels. +// `Matrix` is a tensor of shape `[..., M, M]` whose inner-most 2 dimensions +// form square matrices. `Rhs` is a tensor of shape `[..., M, K]`. The `output` is +// a tensor shape `[..., M, K]`. If `adjoint` is `False` then each output matrix +// satisfies `matrix[..., :, :] * output[..., :, :] = rhs[..., :, :]`. +// If `adjoint` is `True` then each output matrix satisfies +// `adjoint(matrix[..., :, :]) * output[..., :, :] = rhs[..., :, :]`. // // Arguments: -// true_classes: The true_classes output of UnpackSparseLabels. -// sampled_candidates: The sampled_candidates output of CandidateSampler. -// num_true: Number of true labels per context. +// matrix: Shape is `[..., M, M]`. +// rhs: Shape is `[..., M, K]`. // -// Returns A vector of indices corresponding to rows of true_candidates.A vector of IDs of positions in sampled_candidates that match a true_label -// for the row with the corresponding index in indices.A vector of the same length as indices and ids, in which each element -// is -FLOAT_MAX. -func ComputeAccidentalHits(scope *Scope, true_classes tf.Output, sampled_candidates tf.Output, num_true int64, optional ...ComputeAccidentalHitsAttr) (indices tf.Output, ids tf.Output, weights tf.Output) { +// Returns Shape is `[..., M, K]`. +func MatrixSolve(scope *Scope, matrix tf.Output, rhs tf.Output, optional ...MatrixSolveAttr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"num_true": num_true} + attrs := map[string]interface{}{} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "ComputeAccidentalHits", + Type: "MatrixSolve", Input: []tf.Input{ - true_classes, sampled_candidates, + matrix, rhs, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) + return op.Output(0) } -// TensorArrayGatherV3Attr is an optional argument to TensorArrayGatherV3. -type TensorArrayGatherV3Attr func(optionalAttr) - -// TensorArrayGatherV3ElementShape sets the optional element_shape attribute to value. -// -// value: The expected shape of an element, if known. Used to -// validate the shapes of TensorArray elements. If this shape is not -// fully specified, gathering zero-size TensorArrays is an error. -// If not specified, defaults to -func TensorArrayGatherV3ElementShape(value tf.Shape) TensorArrayGatherV3Attr { - return func(m optionalAttr) { - m["element_shape"] = value +// Computes acos of x element-wise. +func Acos(scope *Scope, x tf.Output) (y tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Acos", + Input: []tf.Input{ + x, + }, } + op := scope.AddOperation(opspec) + return op.Output(0) } -// Gather specific elements from the TensorArray into output `value`. +// Real-valued fast Fourier transform. // -// All elements selected by `indices` must have the same shape. +// Computes the 1-dimensional discrete Fourier transform of a real-valued signal +// over the inner-most dimension of `input`. +// +// Since the DFT of a real signal is Hermitian-symmetric, `RFFT` only returns the +// `fft_length / 2 + 1` unique components of the FFT: the zero-frequency term, +// followed by the `fft_length / 2` positive-frequency terms. +// +// Along the axis `RFFT` is computed on, if `fft_length` is smaller than the +// corresponding dimension of `input`, the dimension is cropped. If it is larger, +// the dimension is padded with zeros. // // Arguments: -// handle: The handle to a TensorArray. -// indices: The locations in the TensorArray from which to read tensor elements. -// flow_in: A float scalar that enforces proper chaining of operations. -// dtype: The type of the elem that is returned. +// input: A float32 tensor. +// fft_length: An int32 tensor of shape [1]. The FFT length. // -// Returns All of the elements in the TensorArray, concatenated along a new -// axis (the new dimension 0). -func TensorArrayGatherV3(scope *Scope, handle tf.Output, indices tf.Output, flow_in tf.Output, dtype tf.DataType, optional ...TensorArrayGatherV3Attr) (value tf.Output) { +// Returns A complex64 tensor of the same rank as `input`. The inner-most +// dimension of `input` is replaced with the `fft_length / 2 + 1` unique +// frequency components of its 1D Fourier transform. +// +// @compatibility(numpy) +// Equivalent to np.fft.rfft +// @end_compatibility +func RFFT(scope *Scope, input tf.Output, fft_length tf.Output) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtype": dtype} - for _, a := range optional { - a(attrs) - } opspec := tf.OpSpec{ - Type: "TensorArrayGatherV3", + Type: "RFFT", Input: []tf.Input{ - handle, indices, flow_in, + input, fft_length, }, - Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) } -// Converts each string in the input Tensor to its hash mod by a number of buckets. -// -// The hash function is deterministic on the content of the string within the -// process and will never change. However, it is not suitable for cryptography. -// This function may be used when CPU time is scarce and inputs are trusted or -// unimportant. There is a risk of adversaries constructing inputs that all hash -// to the same bucket. To prevent this problem, use a strong hash function with -// `tf.string_to_hash_bucket_strong`. -// -// Arguments: -// input: The strings to assign a hash bucket. -// num_buckets: The number of buckets. +// DepthwiseConv2dNativeBackpropFilterAttr is an optional argument to DepthwiseConv2dNativeBackpropFilter. +type DepthwiseConv2dNativeBackpropFilterAttr func(optionalAttr) + +// DepthwiseConv2dNativeBackpropFilterDataFormat sets the optional data_format attribute to value. // -// Returns A Tensor of the same shape as the input `string_tensor`. -func StringToHashBucketFast(scope *Scope, input tf.Output, num_buckets int64) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"num_buckets": num_buckets} - opspec := tf.OpSpec{ - Type: "StringToHashBucketFast", - Input: []tf.Input{ - input, - }, - Attrs: attrs, +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the data is stored in the order of: +// [batch, height, width, channels]. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, channels, height, width]. +// If not specified, defaults to "NHWC" +func DepthwiseConv2dNativeBackpropFilterDataFormat(value string) DepthwiseConv2dNativeBackpropFilterAttr { + return func(m optionalAttr) { + m["data_format"] = value } - op := scope.AddOperation(opspec) - return op.Output(0) } -// Returns the max of x and y (i.e. x > y ? x : y) element-wise. +// DepthwiseConv2dNativeBackpropFilterDilations sets the optional dilations attribute to value. // -// *NOTE*: `Maximum` supports broadcasting. More about broadcasting -// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) -func Maximum(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Maximum", - Input: []tf.Input{ - x, y, - }, +// value: 1-D tensor of length 4. The dilation factor for each dimension of +// `input`. If set to k > 1, there will be k-1 skipped cells between each filter +// element on that dimension. The dimension order is determined by the value of +// `data_format`, see above for details. Dilations in the batch and depth +// dimensions must be 1. +// If not specified, defaults to +func DepthwiseConv2dNativeBackpropFilterDilations(value []int64) DepthwiseConv2dNativeBackpropFilterAttr { + return func(m optionalAttr) { + m["dilations"] = value } - op := scope.AddOperation(opspec) - return op.Output(0) } -// Real-valued fast Fourier transform. -// -// Computes the 1-dimensional discrete Fourier transform of a real-valued signal -// over the inner-most dimension of `input`. -// -// Since the DFT of a real signal is Hermitian-symmetric, `RFFT` only returns the -// `fft_length / 2 + 1` unique components of the FFT: the zero-frequency term, -// followed by the `fft_length / 2` positive-frequency terms. -// -// Along the axis `RFFT` is computed on, if `fft_length` is smaller than the -// corresponding dimension of `input`, the dimension is cropped. If it is larger, -// the dimension is padded with zeros. +// Computes the gradients of depthwise convolution with respect to the filter. // // Arguments: -// input: A float32 tensor. -// fft_length: An int32 tensor of shape [1]. The FFT length. -// -// Returns A complex64 tensor of the same rank as `input`. The inner-most -// dimension of `input` is replaced with the `fft_length / 2 + 1` unique -// frequency components of its 1D Fourier transform. +// input: 4-D with shape based on `data_format`. For example, if +// `data_format` is 'NHWC' then `input` is a 4-D `[batch, in_height, +// in_width, in_channels]` tensor. +// filter_sizes: An integer vector representing the tensor shape of `filter`, +// where `filter` is a 4-D +// `[filter_height, filter_width, in_channels, depthwise_multiplier]` tensor. +// out_backprop: 4-D with shape based on `data_format`. +// For example, if `data_format` is 'NHWC' then +// out_backprop shape is `[batch, out_height, out_width, out_channels]`. +// Gradients w.r.t. the output of the convolution. +// strides: The stride of the sliding window for each dimension of the input +// of the convolution. +// padding: The type of padding algorithm to use. // -// @compatibility(numpy) -// Equivalent to np.fft.rfft -// @end_compatibility -func RFFT(scope *Scope, input tf.Output, fft_length tf.Output) (output tf.Output) { +// Returns 4-D with shape +// `[filter_height, filter_width, in_channels, out_channels]`. Gradient w.r.t. +// the `filter` input of the convolution. +func DepthwiseConv2dNativeBackpropFilter(scope *Scope, input tf.Output, filter_sizes tf.Output, out_backprop tf.Output, strides []int64, padding string, optional ...DepthwiseConv2dNativeBackpropFilterAttr) (output tf.Output) { if scope.Err() != nil { return } + attrs := map[string]interface{}{"strides": strides, "padding": padding} + for _, a := range optional { + a(attrs) + } opspec := tf.OpSpec{ - Type: "RFFT", + Type: "DepthwiseConv2dNativeBackpropFilter", Input: []tf.Input{ - input, fft_length, + input, filter_sizes, out_backprop, }, + Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) @@ -6236,6 +6046,79 @@ func Tan(scope *Scope, x tf.Output) (y tf.Output) { return op.Output(0) } +// ResourceSparseApplyFtrlAttr is an optional argument to ResourceSparseApplyFtrl. +type ResourceSparseApplyFtrlAttr func(optionalAttr) + +// ResourceSparseApplyFtrlUseLocking sets the optional use_locking attribute to value. +// +// value: If `True`, updating of the var and accum tensors will be protected +// by a lock; otherwise the behavior is undefined, but may exhibit less +// contention. +// If not specified, defaults to false +func ResourceSparseApplyFtrlUseLocking(value bool) ResourceSparseApplyFtrlAttr { + return func(m optionalAttr) { + m["use_locking"] = value + } +} + +// Update relevant entries in '*var' according to the Ftrl-proximal scheme. +// +// That is for rows we have grad for, we update var, accum and linear as follows: +// accum_new = accum + grad * grad +// linear += grad + (accum_new^(-lr_power) - accum^(-lr_power)) / lr * var +// quadratic = 1.0 / (accum_new^(lr_power) * lr) + 2 * l2 +// var = (sign(linear) * l1 - linear) / quadratic if |linear| > l1 else 0.0 +// accum = accum_new +// +// Arguments: +// var_: Should be from a Variable(). +// accum: Should be from a Variable(). +// linear: Should be from a Variable(). +// grad: The gradient. +// indices: A vector of indices into the first dimension of var and accum. +// lr: Scaling factor. Must be a scalar. +// l1: L1 regularization. Must be a scalar. +// l2: L2 regularization. Must be a scalar. +// lr_power: Scaling factor. Must be a scalar. +// +// Returns the created operation. +func ResourceSparseApplyFtrl(scope *Scope, var_ tf.Output, accum tf.Output, linear tf.Output, grad tf.Output, indices tf.Output, lr tf.Output, l1 tf.Output, l2 tf.Output, lr_power tf.Output, optional ...ResourceSparseApplyFtrlAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ResourceSparseApplyFtrl", + Input: []tf.Input{ + var_, accum, linear, grad, indices, lr, l1, l2, lr_power, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + +// Returns which elements of x are Inf. +// +// @compatibility(numpy) +// Equivalent to np.isinf +// @end_compatibility +func IsInf(scope *Scope, x tf.Output) (y tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "IsInf", + Input: []tf.Input{ + x, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Computes the sum along sparse segments of a tensor divided by the sqrt of N. // // N is the size of the segment being reduced. @@ -6918,32 +6801,196 @@ func ResourceScatterUpdate(scope *Scope, resource tf.Output, indices tf.Output, return scope.AddOperation(opspec) } -// CumsumAttr is an optional argument to Cumsum. -type CumsumAttr func(optionalAttr) +// AvgPoolGradAttr is an optional argument to AvgPoolGrad. +type AvgPoolGradAttr func(optionalAttr) -// CumsumExclusive sets the optional exclusive attribute to value. +// AvgPoolGradDataFormat sets the optional data_format attribute to value. // -// value: If `True`, perform exclusive cumsum. -// If not specified, defaults to false -func CumsumExclusive(value bool) CumsumAttr { +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the data is stored in the order of: +// [batch, in_height, in_width, in_channels]. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, in_channels, in_height, in_width]. +// If not specified, defaults to "NHWC" +func AvgPoolGradDataFormat(value string) AvgPoolGradAttr { return func(m optionalAttr) { - m["exclusive"] = value + m["data_format"] = value } } -// CumsumReverse sets the optional reverse attribute to value. +// Computes gradients of the average pooling function. // -// value: A `bool` (default: False). -// If not specified, defaults to false -func CumsumReverse(value bool) CumsumAttr { - return func(m optionalAttr) { - m["reverse"] = value - } -} - -// Compute the cumulative sum of the tensor `x` along `axis`. +// Arguments: +// orig_input_shape: 1-D. Shape of the original input to `avg_pool`. +// grad: 4-D with shape `[batch, height, width, channels]`. Gradients w.r.t. +// the output of `avg_pool`. +// ksize: The size of the sliding window for each dimension of the input. +// strides: The stride of the sliding window for each dimension of the input. +// padding: The type of padding algorithm to use. // -// By default, this op performs an inclusive cumsum, which means that the first +// Returns 4-D. Gradients w.r.t. the input of `avg_pool`. +func AvgPoolGrad(scope *Scope, orig_input_shape tf.Output, grad tf.Output, ksize []int64, strides []int64, padding string, optional ...AvgPoolGradAttr) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "AvgPoolGrad", + Input: []tf.Input{ + orig_input_shape, grad, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// StageClearAttr is an optional argument to StageClear. +type StageClearAttr func(optionalAttr) + +// StageClearCapacity sets the optional capacity attribute to value. +// If not specified, defaults to 0 +// +// REQUIRES: value >= 0 +func StageClearCapacity(value int64) StageClearAttr { + return func(m optionalAttr) { + m["capacity"] = value + } +} + +// StageClearMemoryLimit sets the optional memory_limit attribute to value. +// If not specified, defaults to 0 +// +// REQUIRES: value >= 0 +func StageClearMemoryLimit(value int64) StageClearAttr { + return func(m optionalAttr) { + m["memory_limit"] = value + } +} + +// StageClearContainer sets the optional container attribute to value. +// If not specified, defaults to "" +func StageClearContainer(value string) StageClearAttr { + return func(m optionalAttr) { + m["container"] = value + } +} + +// StageClearSharedName sets the optional shared_name attribute to value. +// If not specified, defaults to "" +func StageClearSharedName(value string) StageClearAttr { + return func(m optionalAttr) { + m["shared_name"] = value + } +} + +// Op removes all elements in the underlying container. +// +// Returns the created operation. +func StageClear(scope *Scope, dtypes []tf.DataType, optional ...StageClearAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"dtypes": dtypes} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "StageClear", + + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + +// ComputeAccidentalHitsAttr is an optional argument to ComputeAccidentalHits. +type ComputeAccidentalHitsAttr func(optionalAttr) + +// ComputeAccidentalHitsSeed sets the optional seed attribute to value. +// +// value: If either seed or seed2 are set to be non-zero, the random number +// generator is seeded by the given seed. Otherwise, it is seeded by a +// random seed. +// If not specified, defaults to 0 +func ComputeAccidentalHitsSeed(value int64) ComputeAccidentalHitsAttr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// ComputeAccidentalHitsSeed2 sets the optional seed2 attribute to value. +// +// value: An second seed to avoid seed collision. +// If not specified, defaults to 0 +func ComputeAccidentalHitsSeed2(value int64) ComputeAccidentalHitsAttr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// Computes the ids of the positions in sampled_candidates that match true_labels. +// +// When doing log-odds NCE, the result of this op should be passed through a +// SparseToDense op, then added to the logits of the sampled candidates. This has +// the effect of 'removing' the sampled labels that match the true labels by +// making the classifier sure that they are sampled labels. +// +// Arguments: +// true_classes: The true_classes output of UnpackSparseLabels. +// sampled_candidates: The sampled_candidates output of CandidateSampler. +// num_true: Number of true labels per context. +// +// Returns A vector of indices corresponding to rows of true_candidates.A vector of IDs of positions in sampled_candidates that match a true_label +// for the row with the corresponding index in indices.A vector of the same length as indices and ids, in which each element +// is -FLOAT_MAX. +func ComputeAccidentalHits(scope *Scope, true_classes tf.Output, sampled_candidates tf.Output, num_true int64, optional ...ComputeAccidentalHitsAttr) (indices tf.Output, ids tf.Output, weights tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"num_true": num_true} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ComputeAccidentalHits", + Input: []tf.Input{ + true_classes, sampled_candidates, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2) +} + +// CumsumAttr is an optional argument to Cumsum. +type CumsumAttr func(optionalAttr) + +// CumsumExclusive sets the optional exclusive attribute to value. +// +// value: If `True`, perform exclusive cumsum. +// If not specified, defaults to false +func CumsumExclusive(value bool) CumsumAttr { + return func(m optionalAttr) { + m["exclusive"] = value + } +} + +// CumsumReverse sets the optional reverse attribute to value. +// +// value: A `bool` (default: False). +// If not specified, defaults to false +func CumsumReverse(value bool) CumsumAttr { + return func(m optionalAttr) { + m["reverse"] = value + } +} + +// Compute the cumulative sum of the tensor `x` along `axis`. +// +// By default, this op performs an inclusive cumsum, which means that the first // element of the input is identical to the first element of the output: // // ```python @@ -7314,79 +7361,6 @@ func StringToHashBucketStrong(scope *Scope, input tf.Output, num_buckets int64, return op.Output(0) } -// Generates values in an interval. -// -// A sequence of `num` evenly-spaced values are generated beginning at `start`. -// If `num > 1`, the values in the sequence increase by `stop - start / num - 1`, -// so that the last one is exactly `stop`. -// -// For example: -// -// ``` -// tf.linspace(10.0, 12.0, 3, name="linspace") => [ 10.0 11.0 12.0] -// ``` -// -// Arguments: -// start: First entry in the range. -// stop: Last entry in the range. -// num: Number of values to generate. -// -// Returns 1-D. The generated values. -func LinSpace(scope *Scope, start tf.Output, stop tf.Output, num tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "LinSpace", - Input: []tf.Input{ - start, stop, num, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// DestroyResourceOpAttr is an optional argument to DestroyResourceOp. -type DestroyResourceOpAttr func(optionalAttr) - -// DestroyResourceOpIgnoreLookupError sets the optional ignore_lookup_error attribute to value. -// -// value: whether to ignore the error when the resource -// doesn't exist. -// If not specified, defaults to true -func DestroyResourceOpIgnoreLookupError(value bool) DestroyResourceOpAttr { - return func(m optionalAttr) { - m["ignore_lookup_error"] = value - } -} - -// Deletes the resource specified by the handle. -// -// All subsequent operations using the resource will result in a NotFound -// error status. -// -// Arguments: -// resource: handle to the resource to delete. -// -// Returns the created operation. -func DestroyResourceOp(scope *Scope, resource tf.Output, optional ...DestroyResourceOpAttr) (o *tf.Operation) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "DestroyResourceOp", - Input: []tf.Input{ - resource, - }, - Attrs: attrs, - } - return scope.AddOperation(opspec) -} - // Applies softmax to a batched N-D `SparseTensor`. // // The inputs represent an N-D SparseTensor with logical shape `[..., B, C]` @@ -7822,24 +7796,97 @@ func IFFT(scope *Scope, input tf.Output) (output tf.Output) { return op.Output(0) } -// LRNAttr is an optional argument to LRN. -type LRNAttr func(optionalAttr) - -// LRNDepthRadius sets the optional depth_radius attribute to value. +// Generates values in an interval. // -// value: 0-D. Half-width of the 1-D normalization window. -// If not specified, defaults to 5 -func LRNDepthRadius(value int64) LRNAttr { - return func(m optionalAttr) { - m["depth_radius"] = value - } -} - -// LRNBias sets the optional bias attribute to value. +// A sequence of `num` evenly-spaced values are generated beginning at `start`. +// If `num > 1`, the values in the sequence increase by `stop - start / num - 1`, +// so that the last one is exactly `stop`. // -// value: An offset (usually positive to avoid dividing by 0). -// If not specified, defaults to 1 -func LRNBias(value float32) LRNAttr { +// For example: +// +// ``` +// tf.linspace(10.0, 12.0, 3, name="linspace") => [ 10.0 11.0 12.0] +// ``` +// +// Arguments: +// start: First entry in the range. +// stop: Last entry in the range. +// num: Number of values to generate. +// +// Returns 1-D. The generated values. +func LinSpace(scope *Scope, start tf.Output, stop tf.Output, num tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "LinSpace", + Input: []tf.Input{ + start, stop, num, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// DestroyResourceOpAttr is an optional argument to DestroyResourceOp. +type DestroyResourceOpAttr func(optionalAttr) + +// DestroyResourceOpIgnoreLookupError sets the optional ignore_lookup_error attribute to value. +// +// value: whether to ignore the error when the resource +// doesn't exist. +// If not specified, defaults to true +func DestroyResourceOpIgnoreLookupError(value bool) DestroyResourceOpAttr { + return func(m optionalAttr) { + m["ignore_lookup_error"] = value + } +} + +// Deletes the resource specified by the handle. +// +// All subsequent operations using the resource will result in a NotFound +// error status. +// +// Arguments: +// resource: handle to the resource to delete. +// +// Returns the created operation. +func DestroyResourceOp(scope *Scope, resource tf.Output, optional ...DestroyResourceOpAttr) (o *tf.Operation) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "DestroyResourceOp", + Input: []tf.Input{ + resource, + }, + Attrs: attrs, + } + return scope.AddOperation(opspec) +} + +// LRNAttr is an optional argument to LRN. +type LRNAttr func(optionalAttr) + +// LRNDepthRadius sets the optional depth_radius attribute to value. +// +// value: 0-D. Half-width of the 1-D normalization window. +// If not specified, defaults to 5 +func LRNDepthRadius(value int64) LRNAttr { + return func(m optionalAttr) { + m["depth_radius"] = value + } +} + +// LRNBias sets the optional bias attribute to value. +// +// value: An offset (usually positive to avoid dividing by 0). +// If not specified, defaults to 1 +func LRNBias(value float32) LRNAttr { return func(m optionalAttr) { m["bias"] = value } @@ -8054,6 +8101,65 @@ func ResizeArea(scope *Scope, images tf.Output, size tf.Output, optional ...Resi return op.Output(0) } +// Pads a tensor with zeros. +// +// This operation pads a `input` with zeros according to the `paddings` you +// specify. `paddings` is an integer tensor with shape `[Dn, 2]`, where n is the +// rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates +// how many zeros to add before the contents of `input` in that dimension, and +// `paddings[D, 1]` indicates how many zeros to add after the contents of `input` +// in that dimension. +// +// The padded size of each dimension D of the output is: +// +// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` +// +// For example: +// +// ``` +// # 't' is [[1, 1], [2, 2]] +// # 'paddings' is [[1, 1], [2, 2]] +// # rank of 't' is 2 +// pad(t, paddings) ==> [[0, 0, 0, 0, 0, 0] +// [0, 0, 1, 1, 0, 0] +// [0, 0, 2, 2, 0, 0] +// [0, 0, 0, 0, 0, 0]] +// ``` +func Pad(scope *Scope, input tf.Output, paddings tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Pad", + Input: []tf.Input{ + input, paddings, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Checks whether a resource handle-based variable has been initialized. +// +// Arguments: +// resource: the input resource handle. +// +// Returns a scalar boolean which is true if the variable has been +// initialized. +func VarIsInitializedOp(scope *Scope, resource tf.Output) (is_initialized tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "VarIsInitializedOp", + Input: []tf.Input{ + resource, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // StatelessRandomUniformAttr is an optional argument to StatelessRandomUniform. type StatelessRandomUniformAttr func(optionalAttr) @@ -8098,6 +8204,38 @@ func StatelessRandomUniform(scope *Scope, shape tf.Output, seed tf.Output, optio return op.Output(0) } +// Makes its input available to the next iteration. +// +// Arguments: +// data: The tensor to be made available to the next iteration. +// +// Returns The same tensor as `data`. +func NextIteration(scope *Scope, data tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "NextIteration", + Input: []tf.Input{ + data, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Output a fact about factorials. +func Fact(scope *Scope) (fact tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Fact", + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // AngleAttr is an optional argument to Angle. type AngleAttr func(optionalAttr) @@ -8672,79 +8810,6 @@ func SparseDenseCwiseMul(scope *Scope, sp_indices tf.Output, sp_values tf.Output return op.Output(0) } -// ResourceSparseApplyFtrlAttr is an optional argument to ResourceSparseApplyFtrl. -type ResourceSparseApplyFtrlAttr func(optionalAttr) - -// ResourceSparseApplyFtrlUseLocking sets the optional use_locking attribute to value. -// -// value: If `True`, updating of the var and accum tensors will be protected -// by a lock; otherwise the behavior is undefined, but may exhibit less -// contention. -// If not specified, defaults to false -func ResourceSparseApplyFtrlUseLocking(value bool) ResourceSparseApplyFtrlAttr { - return func(m optionalAttr) { - m["use_locking"] = value - } -} - -// Update relevant entries in '*var' according to the Ftrl-proximal scheme. -// -// That is for rows we have grad for, we update var, accum and linear as follows: -// accum_new = accum + grad * grad -// linear += grad + (accum_new^(-lr_power) - accum^(-lr_power)) / lr * var -// quadratic = 1.0 / (accum_new^(lr_power) * lr) + 2 * l2 -// var = (sign(linear) * l1 - linear) / quadratic if |linear| > l1 else 0.0 -// accum = accum_new -// -// Arguments: -// var_: Should be from a Variable(). -// accum: Should be from a Variable(). -// linear: Should be from a Variable(). -// grad: The gradient. -// indices: A vector of indices into the first dimension of var and accum. -// lr: Scaling factor. Must be a scalar. -// l1: L1 regularization. Must be a scalar. -// l2: L2 regularization. Must be a scalar. -// lr_power: Scaling factor. Must be a scalar. -// -// Returns the created operation. -func ResourceSparseApplyFtrl(scope *Scope, var_ tf.Output, accum tf.Output, linear tf.Output, grad tf.Output, indices tf.Output, lr tf.Output, l1 tf.Output, l2 tf.Output, lr_power tf.Output, optional ...ResourceSparseApplyFtrlAttr) (o *tf.Operation) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "ResourceSparseApplyFtrl", - Input: []tf.Input{ - var_, accum, linear, grad, indices, lr, l1, l2, lr_power, - }, - Attrs: attrs, - } - return scope.AddOperation(opspec) -} - -// Returns which elements of x are Inf. -// -// @compatibility(numpy) -// Equivalent to np.isinf -// @end_compatibility -func IsInf(scope *Scope, x tf.Output) (y tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "IsInf", - Input: []tf.Input{ - x, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // ResourceSparseApplyRMSPropAttr is an optional argument to ResourceSparseApplyRMSProp. type ResourceSparseApplyRMSPropAttr func(optionalAttr) @@ -8974,12 +9039,106 @@ func SampleDistortedBoundingBox(scope *Scope, image_size tf.Output, bounding_box return op.Output(0), op.Output(1), op.Output(2) } -// Returns x / y element-wise for integer types. -// -// Truncation designates that negative numbers will round fractional quantities -// toward zero. I.e. -7 / 5 = -1. This matches C semantics but it is different -// than Python semantics. See `FloorDiv` for a division function that matches -// Python Semantics. +// Converts each string in the input Tensor to its hash mod by a number of buckets. +// +// The hash function is deterministic on the content of the string within the +// process and will never change. However, it is not suitable for cryptography. +// This function may be used when CPU time is scarce and inputs are trusted or +// unimportant. There is a risk of adversaries constructing inputs that all hash +// to the same bucket. To prevent this problem, use a strong hash function with +// `tf.string_to_hash_bucket_strong`. +// +// Arguments: +// input: The strings to assign a hash bucket. +// num_buckets: The number of buckets. +// +// Returns A Tensor of the same shape as the input `string_tensor`. +func StringToHashBucketFast(scope *Scope, input tf.Output, num_buckets int64) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"num_buckets": num_buckets} + opspec := tf.OpSpec{ + Type: "StringToHashBucketFast", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Returns the max of x and y (i.e. x > y ? x : y) element-wise. +// +// *NOTE*: `Maximum` supports broadcasting. More about broadcasting +// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +func Maximum(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "Maximum", + Input: []tf.Input{ + x, y, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// TensorArrayGatherV3Attr is an optional argument to TensorArrayGatherV3. +type TensorArrayGatherV3Attr func(optionalAttr) + +// TensorArrayGatherV3ElementShape sets the optional element_shape attribute to value. +// +// value: The expected shape of an element, if known. Used to +// validate the shapes of TensorArray elements. If this shape is not +// fully specified, gathering zero-size TensorArrays is an error. +// If not specified, defaults to +func TensorArrayGatherV3ElementShape(value tf.Shape) TensorArrayGatherV3Attr { + return func(m optionalAttr) { + m["element_shape"] = value + } +} + +// Gather specific elements from the TensorArray into output `value`. +// +// All elements selected by `indices` must have the same shape. +// +// Arguments: +// handle: The handle to a TensorArray. +// indices: The locations in the TensorArray from which to read tensor elements. +// flow_in: A float scalar that enforces proper chaining of operations. +// dtype: The type of the elem that is returned. +// +// Returns All of the elements in the TensorArray, concatenated along a new +// axis (the new dimension 0). +func TensorArrayGatherV3(scope *Scope, handle tf.Output, indices tf.Output, flow_in tf.Output, dtype tf.DataType, optional ...TensorArrayGatherV3Attr) (value tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"dtype": dtype} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "TensorArrayGatherV3", + Input: []tf.Input{ + handle, indices, flow_in, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Returns x / y element-wise for integer types. +// +// Truncation designates that negative numbers will round fractional quantities +// toward zero. I.e. -7 / 5 = -1. This matches C semantics but it is different +// than Python semantics. See `FloorDiv` for a division function that matches +// Python Semantics. // // *NOTE*: `TruncateDiv` supports broadcasting. More about broadcasting // [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) @@ -9048,6 +9207,30 @@ func RestoreV2(scope *Scope, prefix tf.Output, tensor_names tf.Output, shape_and return tensors } +// Creates a dataset that skips `count` elements from the `input_dataset`. +// +// Arguments: +// +// count: A scalar representing the number of elements from the `input_dataset` +// that should be skipped. If count is -1, skips everything. +// +// +func SkipDataset(scope *Scope, input_dataset tf.Output, count tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "SkipDataset", + Input: []tf.Input{ + input_dataset, count, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Computes the maximum along segments of a tensor. // // Read @{$math_ops#segmentation$the section on segmentation} for an explanation of @@ -9084,30 +9267,6 @@ func SegmentMax(scope *Scope, data tf.Output, segment_ids tf.Output) (output tf. return op.Output(0) } -// Creates a dataset that skips `count` elements from the `input_dataset`. -// -// Arguments: -// -// count: A scalar representing the number of elements from the `input_dataset` -// that should be skipped. If count is -1, skips everything. -// -// -func SkipDataset(scope *Scope, input_dataset tf.Output, count tf.Output, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"output_types": output_types, "output_shapes": output_shapes} - opspec := tf.OpSpec{ - Type: "SkipDataset", - Input: []tf.Input{ - input_dataset, count, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Computes hyperbolic tangent of `x` element-wise. func Tanh(scope *Scope, x tf.Output) (y tf.Output) { if scope.Err() != nil { @@ -9861,6 +10020,79 @@ func FFT(scope *Scope, input tf.Output) (output tf.Output) { return op.Output(0) } +// Transforms a serialized tensorflow.TensorProto proto into a Tensor. +// +// Arguments: +// serialized: A scalar string containing a serialized TensorProto proto. +// out_type: The type of the serialized tensor. The provided type must match the +// type of the serialized tensor and no implicit conversion will take place. +// +// Returns A Tensor of type `out_type`. +func ParseTensor(scope *Scope, serialized tf.Output, out_type tf.DataType) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"out_type": out_type} + opspec := tf.OpSpec{ + Type: "ParseTensor", + Input: []tf.Input{ + serialized, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// MaxPoolWithArgmaxAttr is an optional argument to MaxPoolWithArgmax. +type MaxPoolWithArgmaxAttr func(optionalAttr) + +// MaxPoolWithArgmaxTargmax sets the optional Targmax attribute to value. +// If not specified, defaults to DT_INT64 +func MaxPoolWithArgmaxTargmax(value tf.DataType) MaxPoolWithArgmaxAttr { + return func(m optionalAttr) { + m["Targmax"] = value + } +} + +// Performs max pooling on the input and outputs both max values and indices. +// +// The indices in `argmax` are flattened, so that a maximum value at position +// `[b, y, x, c]` becomes flattened index +// `((b * height + y) * width + x) * channels + c`. +// +// The indices returned are always in `[0, height) x [0, width)` before flattening, +// even if padding is involved and the mathematically correct answer is outside +// (either negative or too large). This is a bug, but fixing it is difficult to do +// in a safe backwards compatible way, especially due to flattening. +// +// Arguments: +// input: 4-D with shape `[batch, height, width, channels]`. Input to pool over. +// ksize: The size of the window for each dimension of the input tensor. +// strides: The stride of the sliding window for each dimension of the +// input tensor. +// padding: The type of padding algorithm to use. +// +// Returns The max pooled output tensor.4-D. The flattened indices of the max values chosen for each output. +func MaxPoolWithArgmax(scope *Scope, input tf.Output, ksize []int64, strides []int64, padding string, optional ...MaxPoolWithArgmaxAttr) (output tf.Output, argmax tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "MaxPoolWithArgmax", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1) +} + // ResourceSparseApplyAdagradDAAttr is an optional argument to ResourceSparseApplyAdagradDA. type ResourceSparseApplyAdagradDAAttr func(optionalAttr) @@ -11004,104 +11236,37 @@ func FusedResizeAndPadConv2D(scope *Scope, input tf.Output, size tf.Output, padd return op.Output(0) } -// Transforms a Tensor into a serialized TensorProto proto. +// Inverse 3D fast Fourier transform. +// +// Computes the inverse 3-dimensional discrete Fourier transform over the +// inner-most 3 dimensions of `input`. // // Arguments: -// tensor: A Tensor of type `T`. +// input: A complex64 tensor. // -// Returns A serialized TensorProto proto of the input tensor. -func SerializeTensor(scope *Scope, tensor tf.Output) (serialized tf.Output) { +// Returns A complex64 tensor of the same shape as `input`. The inner-most 3 +// dimensions of `input` are replaced with their inverse 3D Fourier transform. +// +// @compatibility(numpy) +// Equivalent to np.fft.ifftn with 3 dimensions. +// @end_compatibility +func IFFT3D(scope *Scope, input tf.Output) (output tf.Output) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "SerializeTensor", + Type: "IFFT3D", Input: []tf.Input{ - tensor, + input, }, } op := scope.AddOperation(opspec) return op.Output(0) } -// MatrixSolveAttr is an optional argument to MatrixSolve. -type MatrixSolveAttr func(optionalAttr) - -// MatrixSolveAdjoint sets the optional adjoint attribute to value. +// Adds `bias` to `value`. // -// value: Boolean indicating whether to solve with `matrix` or its (block-wise) -// adjoint. -// If not specified, defaults to false -func MatrixSolveAdjoint(value bool) MatrixSolveAttr { - return func(m optionalAttr) { - m["adjoint"] = value - } -} - -// Solves systems of linear equations. -// -// `Matrix` is a tensor of shape `[..., M, M]` whose inner-most 2 dimensions -// form square matrices. `Rhs` is a tensor of shape `[..., M, K]`. The `output` is -// a tensor shape `[..., M, K]`. If `adjoint` is `False` then each output matrix -// satisfies `matrix[..., :, :] * output[..., :, :] = rhs[..., :, :]`. -// If `adjoint` is `True` then each output matrix satisfies -// `adjoint(matrix[..., :, :]) * output[..., :, :] = rhs[..., :, :]`. -// -// Arguments: -// matrix: Shape is `[..., M, M]`. -// rhs: Shape is `[..., M, K]`. -// -// Returns Shape is `[..., M, K]`. -func MatrixSolve(scope *Scope, matrix tf.Output, rhs tf.Output, optional ...MatrixSolveAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "MatrixSolve", - Input: []tf.Input{ - matrix, rhs, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Inverse 3D fast Fourier transform. -// -// Computes the inverse 3-dimensional discrete Fourier transform over the -// inner-most 3 dimensions of `input`. -// -// Arguments: -// input: A complex64 tensor. -// -// Returns A complex64 tensor of the same shape as `input`. The inner-most 3 -// dimensions of `input` are replaced with their inverse 3D Fourier transform. -// -// @compatibility(numpy) -// Equivalent to np.fft.ifftn with 3 dimensions. -// @end_compatibility -func IFFT3D(scope *Scope, input tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "IFFT3D", - Input: []tf.Input{ - input, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Adds `bias` to `value`. -// -// This is a deprecated version of BiasAdd and will be soon removed. +// This is a deprecated version of BiasAdd and will be soon removed. // // This is a special case of `tf.add` where `bias` is restricted to be 1-D. // Broadcasting is supported, so `value` may have any number of dimensions. @@ -12025,6 +12190,46 @@ func AddManySparseToTensorsMap(scope *Scope, sparse_indices tf.Output, sparse_va return op.Output(0) } +// Concatenates tensors along one dimension. +// +// Arguments: +// values: List of `N` Tensors to concatenate. Their ranks and types must match, +// and their sizes must match in all dimensions except `concat_dim`. +// axis: 0-D. The dimension along which to concatenate. Must be in the +// range [-rank(values), rank(values)). +// +// Returns A `Tensor` with the concatenation of values stacked along the +// `concat_dim` dimension. This tensor's shape matches that of `values` except +// in `concat_dim` where it has the sum of the sizes. +func ConcatV2(scope *Scope, values []tf.Output, axis tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ConcatV2", + Input: []tf.Input{ + tf.OutputList(values), axis, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Reads and outputs the entire contents of the input filename. +func ReadFile(scope *Scope, filename tf.Output) (contents tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "ReadFile", + Input: []tf.Input{ + filename, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // MinAttr is an optional argument to Min. type MinAttr func(optionalAttr) @@ -12088,76 +12293,6 @@ func Transpose(scope *Scope, x tf.Output, perm tf.Output) (y tf.Output) { return op.Output(0) } -// DepthwiseConv2dNativeBackpropFilterAttr is an optional argument to DepthwiseConv2dNativeBackpropFilter. -type DepthwiseConv2dNativeBackpropFilterAttr func(optionalAttr) - -// DepthwiseConv2dNativeBackpropFilterDataFormat sets the optional data_format attribute to value. -// -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the data is stored in the order of: -// [batch, height, width, channels]. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, channels, height, width]. -// If not specified, defaults to "NHWC" -func DepthwiseConv2dNativeBackpropFilterDataFormat(value string) DepthwiseConv2dNativeBackpropFilterAttr { - return func(m optionalAttr) { - m["data_format"] = value - } -} - -// DepthwiseConv2dNativeBackpropFilterDilations sets the optional dilations attribute to value. -// -// value: 1-D tensor of length 4. The dilation factor for each dimension of -// `input`. If set to k > 1, there will be k-1 skipped cells between each filter -// element on that dimension. The dimension order is determined by the value of -// `data_format`, see above for details. Dilations in the batch and depth -// dimensions must be 1. -// If not specified, defaults to -func DepthwiseConv2dNativeBackpropFilterDilations(value []int64) DepthwiseConv2dNativeBackpropFilterAttr { - return func(m optionalAttr) { - m["dilations"] = value - } -} - -// Computes the gradients of depthwise convolution with respect to the filter. -// -// Arguments: -// input: 4-D with shape based on `data_format`. For example, if -// `data_format` is 'NHWC' then `input` is a 4-D `[batch, in_height, -// in_width, in_channels]` tensor. -// filter_sizes: An integer vector representing the tensor shape of `filter`, -// where `filter` is a 4-D -// `[filter_height, filter_width, in_channels, depthwise_multiplier]` tensor. -// out_backprop: 4-D with shape based on `data_format`. -// For example, if `data_format` is 'NHWC' then -// out_backprop shape is `[batch, out_height, out_width, out_channels]`. -// Gradients w.r.t. the output of the convolution. -// strides: The stride of the sliding window for each dimension of the input -// of the convolution. -// padding: The type of padding algorithm to use. -// -// Returns 4-D with shape -// `[filter_height, filter_width, in_channels, out_channels]`. Gradient w.r.t. -// the `filter` input of the convolution. -func DepthwiseConv2dNativeBackpropFilter(scope *Scope, input tf.Output, filter_sizes tf.Output, out_backprop tf.Output, strides []int64, padding string, optional ...DepthwiseConv2dNativeBackpropFilterAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"strides": strides, "padding": padding} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "DepthwiseConv2dNativeBackpropFilter", - Input: []tf.Input{ - input, filter_sizes, out_backprop, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Computes sigmoid of `x` element-wise. // // Specifically, `y = 1 / (1 + exp(-x))`. @@ -12888,190 +13023,252 @@ func TensorArrayV2(scope *Scope, size tf.Output, dtype tf.DataType, optional ... return op.Output(0) } -// ThreadUnsafeUnigramCandidateSamplerAttr is an optional argument to ThreadUnsafeUnigramCandidateSampler. -type ThreadUnsafeUnigramCandidateSamplerAttr func(optionalAttr) +// DecodeCSVAttr is an optional argument to DecodeCSV. +type DecodeCSVAttr func(optionalAttr) -// ThreadUnsafeUnigramCandidateSamplerSeed sets the optional seed attribute to value. +// DecodeCSVFieldDelim sets the optional field_delim attribute to value. // -// value: If either seed or seed2 are set to be non-zero, the random number -// generator is seeded by the given seed. Otherwise, it is seeded by a -// random seed. -// If not specified, defaults to 0 -func ThreadUnsafeUnigramCandidateSamplerSeed(value int64) ThreadUnsafeUnigramCandidateSamplerAttr { +// value: char delimiter to separate fields in a record. +// If not specified, defaults to "," +func DecodeCSVFieldDelim(value string) DecodeCSVAttr { return func(m optionalAttr) { - m["seed"] = value + m["field_delim"] = value } } -// ThreadUnsafeUnigramCandidateSamplerSeed2 sets the optional seed2 attribute to value. +// DecodeCSVUseQuoteDelim sets the optional use_quote_delim attribute to value. // -// value: An second seed to avoid seed collision. -// If not specified, defaults to 0 -func ThreadUnsafeUnigramCandidateSamplerSeed2(value int64) ThreadUnsafeUnigramCandidateSamplerAttr { +// value: If false, treats double quotation marks as regular +// characters inside of the string fields (ignoring RFC 4180, Section 2, +// Bullet 5). +// If not specified, defaults to true +func DecodeCSVUseQuoteDelim(value bool) DecodeCSVAttr { return func(m optionalAttr) { - m["seed2"] = value + m["use_quote_delim"] = value } } -// Generates labels for candidate sampling with a learned unigram distribution. -// -// See explanations of candidate sampling and the data formats at -// go/candidate-sampling. +// DecodeCSVNaValue sets the optional na_value attribute to value. // -// For each batch, this op picks a single set of sampled candidate labels. +// value: Additional string to recognize as NA/NaN. +// If not specified, defaults to "" +func DecodeCSVNaValue(value string) DecodeCSVAttr { + return func(m optionalAttr) { + m["na_value"] = value + } +} + +// Convert CSV records to tensors. Each column maps to one tensor. // -// The advantages of sampling candidates per-batch are simplicity and the -// possibility of efficient dense matrix multiplication. The disadvantage is that -// the sampled candidates must be chosen independently of the context and of the -// true labels. +// RFC 4180 format is expected for the CSV records. +// (https://tools.ietf.org/html/rfc4180) +// Note that we allow leading and trailing spaces with int or float field. // // Arguments: -// true_classes: A batch_size * num_true matrix, in which each row contains the -// IDs of the num_true target_classes in the corresponding original label. -// num_true: Number of true labels per context. -// num_sampled: Number of candidates to randomly sample. -// unique: If unique is true, we sample with rejection, so that all sampled -// candidates in a batch are unique. This requires some approximation to -// estimate the post-rejection sampling probabilities. -// range_max: The sampler will sample integers from the interval [0, range_max). +// records: Each string is a record/row in the csv and all records should have +// the same format. +// record_defaults: One tensor per column of the input record, with either a +// scalar default value for that column or empty if the column is required. // -// Returns A vector of length num_sampled, in which each element is -// the ID of a sampled candidate.A batch_size * num_true matrix, representing -// the number of times each candidate is expected to occur in a batch -// of sampled candidates. If unique=true, then this is a probability.A vector of length num_sampled, for each sampled -// candidate representing the number of times the candidate is expected -// to occur in a batch of sampled candidates. If unique=true, then this is a -// probability. -func ThreadUnsafeUnigramCandidateSampler(scope *Scope, true_classes tf.Output, num_true int64, num_sampled int64, unique bool, range_max int64, optional ...ThreadUnsafeUnigramCandidateSamplerAttr) (sampled_candidates tf.Output, true_expected_count tf.Output, sampled_expected_count tf.Output) { +// Returns Each tensor will have the same shape as records. +func DecodeCSV(scope *Scope, records tf.Output, record_defaults []tf.Output, optional ...DecodeCSVAttr) (output []tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"num_true": num_true, "num_sampled": num_sampled, "unique": unique, "range_max": range_max} + attrs := map[string]interface{}{} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "ThreadUnsafeUnigramCandidateSampler", + Type: "DecodeCSV", Input: []tf.Input{ - true_classes, + records, tf.OutputList(record_defaults), }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1), op.Output(2) -} - -// MaxPoolV2Attr is an optional argument to MaxPoolV2. -type MaxPoolV2Attr func(optionalAttr) + if scope.Err() != nil { + return + } + var idx int + var err error + if output, idx, err = makeOutputList(op, idx, "output"); err != nil { + scope.UpdateErr("DecodeCSV", err) + return + } + return output +} -// MaxPoolV2DataFormat sets the optional data_format attribute to value. +// MapClearAttr is an optional argument to MapClear. +type MapClearAttr func(optionalAttr) + +// MapClearCapacity sets the optional capacity attribute to value. +// If not specified, defaults to 0 // -// value: Specify the data format of the input and output data. With the -// default format "NHWC", the data is stored in the order of: -// [batch, in_height, in_width, in_channels]. -// Alternatively, the format could be "NCHW", the data storage order of: -// [batch, in_channels, in_height, in_width]. -// If not specified, defaults to "NHWC" -func MaxPoolV2DataFormat(value string) MaxPoolV2Attr { +// REQUIRES: value >= 0 +func MapClearCapacity(value int64) MapClearAttr { return func(m optionalAttr) { - m["data_format"] = value + m["capacity"] = value } } -// Performs max pooling on the input. +// MapClearMemoryLimit sets the optional memory_limit attribute to value. +// If not specified, defaults to 0 // -// Arguments: -// input: 4-D input to pool over. -// ksize: The size of the window for each dimension of the input tensor. -// strides: The stride of the sliding window for each dimension of the -// input tensor. -// padding: The type of padding algorithm to use. +// REQUIRES: value >= 0 +func MapClearMemoryLimit(value int64) MapClearAttr { + return func(m optionalAttr) { + m["memory_limit"] = value + } +} + +// MapClearContainer sets the optional container attribute to value. +// If not specified, defaults to "" +func MapClearContainer(value string) MapClearAttr { + return func(m optionalAttr) { + m["container"] = value + } +} + +// MapClearSharedName sets the optional shared_name attribute to value. +// If not specified, defaults to "" +func MapClearSharedName(value string) MapClearAttr { + return func(m optionalAttr) { + m["shared_name"] = value + } +} + +// Op removes all elements in the underlying container. // -// Returns The max pooled output tensor. -func MaxPoolV2(scope *Scope, input tf.Output, ksize tf.Output, strides tf.Output, padding string, optional ...MaxPoolV2Attr) (output tf.Output) { +// Returns the created operation. +func MapClear(scope *Scope, dtypes []tf.DataType, optional ...MapClearAttr) (o *tf.Operation) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"padding": padding} + attrs := map[string]interface{}{"dtypes": dtypes} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "MaxPoolV2", - Input: []tf.Input{ - input, ksize, strides, - }, + Type: "MapClear", + Attrs: attrs, } - op := scope.AddOperation(opspec) - return op.Output(0) + return scope.AddOperation(opspec) } -// Deprecated. Use TensorArrayReadV3 +// ThreadUnsafeUnigramCandidateSamplerAttr is an optional argument to ThreadUnsafeUnigramCandidateSampler. +type ThreadUnsafeUnigramCandidateSamplerAttr func(optionalAttr) + +// ThreadUnsafeUnigramCandidateSamplerSeed sets the optional seed attribute to value. // -// DEPRECATED at GraphDef version 26: Use TensorArrayReadV3 -func TensorArrayReadV2(scope *Scope, handle tf.Output, index tf.Output, flow_in tf.Output, dtype tf.DataType) (value tf.Output) { +// value: If either seed or seed2 are set to be non-zero, the random number +// generator is seeded by the given seed. Otherwise, it is seeded by a +// random seed. +// If not specified, defaults to 0 +func ThreadUnsafeUnigramCandidateSamplerSeed(value int64) ThreadUnsafeUnigramCandidateSamplerAttr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// ThreadUnsafeUnigramCandidateSamplerSeed2 sets the optional seed2 attribute to value. +// +// value: An second seed to avoid seed collision. +// If not specified, defaults to 0 +func ThreadUnsafeUnigramCandidateSamplerSeed2(value int64) ThreadUnsafeUnigramCandidateSamplerAttr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// Generates labels for candidate sampling with a learned unigram distribution. +// +// See explanations of candidate sampling and the data formats at +// go/candidate-sampling. +// +// For each batch, this op picks a single set of sampled candidate labels. +// +// The advantages of sampling candidates per-batch are simplicity and the +// possibility of efficient dense matrix multiplication. The disadvantage is that +// the sampled candidates must be chosen independently of the context and of the +// true labels. +// +// Arguments: +// true_classes: A batch_size * num_true matrix, in which each row contains the +// IDs of the num_true target_classes in the corresponding original label. +// num_true: Number of true labels per context. +// num_sampled: Number of candidates to randomly sample. +// unique: If unique is true, we sample with rejection, so that all sampled +// candidates in a batch are unique. This requires some approximation to +// estimate the post-rejection sampling probabilities. +// range_max: The sampler will sample integers from the interval [0, range_max). +// +// Returns A vector of length num_sampled, in which each element is +// the ID of a sampled candidate.A batch_size * num_true matrix, representing +// the number of times each candidate is expected to occur in a batch +// of sampled candidates. If unique=true, then this is a probability.A vector of length num_sampled, for each sampled +// candidate representing the number of times the candidate is expected +// to occur in a batch of sampled candidates. If unique=true, then this is a +// probability. +func ThreadUnsafeUnigramCandidateSampler(scope *Scope, true_classes tf.Output, num_true int64, num_sampled int64, unique bool, range_max int64, optional ...ThreadUnsafeUnigramCandidateSamplerAttr) (sampled_candidates tf.Output, true_expected_count tf.Output, sampled_expected_count tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtype": dtype} + attrs := map[string]interface{}{"num_true": num_true, "num_sampled": num_sampled, "unique": unique, "range_max": range_max} + for _, a := range optional { + a(attrs) + } opspec := tf.OpSpec{ - Type: "TensorArrayReadV2", + Type: "ThreadUnsafeUnigramCandidateSampler", Input: []tf.Input{ - handle, index, flow_in, + true_classes, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0) + return op.Output(0), op.Output(1), op.Output(2) } -// Does nothing. Serves as a control trigger for scheduling. -// -// Only useful as a placeholder for control edges. +// MaxPoolV2Attr is an optional argument to MaxPoolV2. +type MaxPoolV2Attr func(optionalAttr) + +// MaxPoolV2DataFormat sets the optional data_format attribute to value. // -// Returns the created operation. -func ControlTrigger(scope *Scope) (o *tf.Operation) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "ControlTrigger", +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the data is stored in the order of: +// [batch, in_height, in_width, in_channels]. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, in_channels, in_height, in_width]. +// If not specified, defaults to "NHWC" +func MaxPoolV2DataFormat(value string) MaxPoolV2Attr { + return func(m optionalAttr) { + m["data_format"] = value } - return scope.AddOperation(opspec) } -// Batch normalization. -// -// DEPRECATED at GraphDef version 9: Use tf.nn.batch_normalization() -// -// This op is deprecated. Prefer `tf.nn.batch_normalization`. +// Performs max pooling on the input. // // Arguments: -// t: A 4D input Tensor. -// m: A 1D mean Tensor with size matching the last dimension of t. -// This is the first output from tf.nn.moments, -// or a saved moving average thereof. -// v: A 1D variance Tensor with size matching the last dimension of t. -// This is the second output from tf.nn.moments, -// or a saved moving average thereof. -// beta: A 1D beta Tensor with size matching the last dimension of t. -// An offset to be added to the normalized tensor. -// gamma: A 1D gamma Tensor with size matching the last dimension of t. -// If "scale_after_normalization" is true, this tensor will be multiplied -// with the normalized tensor. -// variance_epsilon: A small float number to avoid dividing by 0. -// scale_after_normalization: A bool indicating whether the resulted tensor -// needs to be multiplied with gamma. -func BatchNormWithGlobalNormalization(scope *Scope, t tf.Output, m tf.Output, v tf.Output, beta tf.Output, gamma tf.Output, variance_epsilon float32, scale_after_normalization bool) (result tf.Output) { +// input: 4-D input to pool over. +// ksize: The size of the window for each dimension of the input tensor. +// strides: The stride of the sliding window for each dimension of the +// input tensor. +// padding: The type of padding algorithm to use. +// +// Returns The max pooled output tensor. +func MaxPoolV2(scope *Scope, input tf.Output, ksize tf.Output, strides tf.Output, padding string, optional ...MaxPoolV2Attr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"variance_epsilon": variance_epsilon, "scale_after_normalization": scale_after_normalization} + attrs := map[string]interface{}{"padding": padding} + for _, a := range optional { + a(attrs) + } opspec := tf.OpSpec{ - Type: "BatchNormWithGlobalNormalization", + Type: "MaxPoolV2", Input: []tf.Input{ - t, m, v, beta, gamma, + input, ksize, strides, }, Attrs: attrs, } @@ -13375,99 +13572,40 @@ func TextLineDataset(scope *Scope, filenames tf.Output, compression_type tf.Outp return op.Output(0) } -// Checks whether a resource handle-based variable has been initialized. +// Computes gradients for SparseSegmentMean. // -// Arguments: -// resource: the input resource handle. +// Returns tensor "output" with same shape as grad, except for dimension 0 whose +// value is output_dim0. // -// Returns a scalar boolean which is true if the variable has been -// initialized. -func VarIsInitializedOp(scope *Scope, resource tf.Output) (is_initialized tf.Output) { +// Arguments: +// grad: gradient propagated to the SparseSegmentMean op. +// indices: indices passed to the corresponding SparseSegmentMean op. +// segment_ids: segment_ids passed to the corresponding SparseSegmentMean op. +// output_dim0: dimension 0 of "data" passed to SparseSegmentMean op. +func SparseSegmentMeanGrad(scope *Scope, grad tf.Output, indices tf.Output, segment_ids tf.Output, output_dim0 tf.Output) (output tf.Output) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "VarIsInitializedOp", + Type: "SparseSegmentMeanGrad", Input: []tf.Input{ - resource, + grad, indices, segment_ids, output_dim0, }, } op := scope.AddOperation(opspec) return op.Output(0) } -// Pads a tensor with zeros. -// -// This operation pads a `input` with zeros according to the `paddings` you -// specify. `paddings` is an integer tensor with shape `[Dn, 2]`, where n is the -// rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates -// how many zeros to add before the contents of `input` in that dimension, and -// `paddings[D, 1]` indicates how many zeros to add after the contents of `input` -// in that dimension. -// -// The padded size of each dimension D of the output is: -// -// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` -// -// For example: +// Returns the truth value of (x >= y) element-wise. // -// ``` -// # 't' is [[1, 1], [2, 2]] -// # 'paddings' is [[1, 1], [2, 2]] -// # rank of 't' is 2 -// pad(t, paddings) ==> [[0, 0, 0, 0, 0, 0] -// [0, 0, 1, 1, 0, 0] -// [0, 0, 2, 2, 0, 0] -// [0, 0, 0, 0, 0, 0]] -// ``` -func Pad(scope *Scope, input tf.Output, paddings tf.Output) (output tf.Output) { +// *NOTE*: `GreaterEqual` supports broadcasting. More about broadcasting +// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +func GreaterEqual(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "Pad", - Input: []tf.Input{ - input, paddings, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Computes gradients for SparseSegmentMean. -// -// Returns tensor "output" with same shape as grad, except for dimension 0 whose -// value is output_dim0. -// -// Arguments: -// grad: gradient propagated to the SparseSegmentMean op. -// indices: indices passed to the corresponding SparseSegmentMean op. -// segment_ids: segment_ids passed to the corresponding SparseSegmentMean op. -// output_dim0: dimension 0 of "data" passed to SparseSegmentMean op. -func SparseSegmentMeanGrad(scope *Scope, grad tf.Output, indices tf.Output, segment_ids tf.Output, output_dim0 tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "SparseSegmentMeanGrad", - Input: []tf.Input{ - grad, indices, segment_ids, output_dim0, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Returns the truth value of (x >= y) element-wise. -// -// *NOTE*: `GreaterEqual` supports broadcasting. More about broadcasting -// [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) -func GreaterEqual(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "GreaterEqual", + Type: "GreaterEqual", Input: []tf.Input{ x, y, }, @@ -14876,6 +15014,101 @@ func MatchingFiles(scope *Scope, pattern tf.Output) (filenames tf.Output) { return op.Output(0) } +// MatrixSolveLsAttr is an optional argument to MatrixSolveLs. +type MatrixSolveLsAttr func(optionalAttr) + +// MatrixSolveLsFast sets the optional fast attribute to value. +// If not specified, defaults to true +func MatrixSolveLsFast(value bool) MatrixSolveLsAttr { + return func(m optionalAttr) { + m["fast"] = value + } +} + +// Solves one or more linear least-squares problems. +// +// `matrix` is a tensor of shape `[..., M, N]` whose inner-most 2 dimensions +// form real or complex matrices of size `[M, N]`. `Rhs` is a tensor of the same +// type as `matrix` and shape `[..., M, K]`. +// The output is a tensor shape `[..., N, K]` where each output matrix solves +// each of the equations +// `matrix[..., :, :]` * `output[..., :, :]` = `rhs[..., :, :]` +// in the least squares sense. +// +// We use the following notation for (complex) matrix and right-hand sides +// in the batch: +// +// `matrix`=\\(A \in \mathbb{C}^{m \times n}\\), +// `rhs`=\\(B \in \mathbb{C}^{m \times k}\\), +// `output`=\\(X \in \mathbb{C}^{n \times k}\\), +// `l2_regularizer`=\\(\lambda \in \mathbb{R}\\). +// +// If `fast` is `True`, then the solution is computed by solving the normal +// equations using Cholesky decomposition. Specifically, if \\(m \ge n\\) then +// \\(X = (A^H A + \lambda I)^{-1} A^H B\\), which solves the least-squares +// problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + +// \lambda ||Z||_F^2\\). If \\(m \lt n\\) then `output` is computed as +// \\(X = A^H (A A^H + \lambda I)^{-1} B\\), which (for \\(\lambda = 0\\)) is the +// minimum-norm solution to the under-determined linear system, i.e. +// \\(X = \mathrm{argmin}_{Z \in \mathbb{C}^{n \times k} } ||Z||_F^2 \\), +// subject to \\(A Z = B\\). Notice that the fast path is only numerically stable +// when \\(A\\) is numerically full rank and has a condition number +// \\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or\\(\lambda\\) is +// sufficiently large. +// +// If `fast` is `False` an algorithm based on the numerically robust complete +// orthogonal decomposition is used. This computes the minimum-norm +// least-squares solution, even when \\(A\\) is rank deficient. This path is +// typically 6-7 times slower than the fast path. If `fast` is `False` then +// `l2_regularizer` is ignored. +// +// Arguments: +// matrix: Shape is `[..., M, N]`. +// rhs: Shape is `[..., M, K]`. +// l2_regularizer: Scalar tensor. +// +// @compatibility(numpy) +// Equivalent to np.linalg.lstsq +// @end_compatibility +// +// Returns Shape is `[..., N, K]`. +func MatrixSolveLs(scope *Scope, matrix tf.Output, rhs tf.Output, l2_regularizer tf.Output, optional ...MatrixSolveLsAttr) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "MatrixSolveLs", + Input: []tf.Input{ + matrix, rhs, l2_regularizer, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// Elementwise computes the bitwise OR of `x` and `y`. +// +// The result will have those bits set, that are set in `x`, `y` or both. The +// computation is performed on the underlying representations of `x` and `y`. +func BitwiseOr(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "BitwiseOr", + Input: []tf.Input{ + x, y, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // SparseToSparseSetOperationAttr is an optional argument to SparseToSparseSetOperation. type SparseToSparseSetOperationAttr func(optionalAttr) @@ -15174,6 +15407,52 @@ func TakeManySparseFromTensorsMap(scope *Scope, sparse_handles tf.Output, dtype return op.Output(0), op.Output(1), op.Output(2) } +// MaxPoolAttr is an optional argument to MaxPool. +type MaxPoolAttr func(optionalAttr) + +// MaxPoolDataFormat sets the optional data_format attribute to value. +// +// value: Specify the data format of the input and output data. With the +// default format "NHWC", the data is stored in the order of: +// [batch, in_height, in_width, in_channels]. +// Alternatively, the format could be "NCHW", the data storage order of: +// [batch, in_channels, in_height, in_width]. +// If not specified, defaults to "NHWC" +func MaxPoolDataFormat(value string) MaxPoolAttr { + return func(m optionalAttr) { + m["data_format"] = value + } +} + +// Performs max pooling on the input. +// +// Arguments: +// input: 4-D input to pool over. +// ksize: The size of the window for each dimension of the input tensor. +// strides: The stride of the sliding window for each dimension of the +// input tensor. +// padding: The type of padding algorithm to use. +// +// Returns The max pooled output tensor. +func MaxPool(scope *Scope, input tf.Output, ksize []int64, strides []int64, padding string, optional ...MaxPoolAttr) (output tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "MaxPool", + Input: []tf.Input{ + input, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // Says whether the targets are in the top `K` predictions. // // This outputs a `batch_size` bool array, an entry `out[i]` is `true` if the @@ -16313,106 +16592,29 @@ func Minimum(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { return op.Output(0) } -// MfccAttr is an optional argument to Mfcc. -type MfccAttr func(optionalAttr) - -// MfccUpperFrequencyLimit sets the optional upper_frequency_limit attribute to value. -// -// value: The highest frequency to use when calculating the -// ceptstrum. -// If not specified, defaults to 4000 -func MfccUpperFrequencyLimit(value float32) MfccAttr { - return func(m optionalAttr) { - m["upper_frequency_limit"] = value - } -} - -// MfccLowerFrequencyLimit sets the optional lower_frequency_limit attribute to value. -// -// value: The lowest frequency to use when calculating the -// ceptstrum. -// If not specified, defaults to 20 -func MfccLowerFrequencyLimit(value float32) MfccAttr { - return func(m optionalAttr) { - m["lower_frequency_limit"] = value - } -} - -// MfccFilterbankChannelCount sets the optional filterbank_channel_count attribute to value. +// Returns the element-wise sum of a list of tensors. // -// value: Resolution of the Mel bank used internally. -// If not specified, defaults to 40 -func MfccFilterbankChannelCount(value int64) MfccAttr { - return func(m optionalAttr) { - m["filterbank_channel_count"] = value - } -} - -// MfccDctCoefficientCount sets the optional dct_coefficient_count attribute to value. +// `tf.accumulate_n_v2` performs the same operation as `tf.add_n`, but does not +// wait for all of its inputs to be ready before beginning to sum. This can +// save memory if inputs are ready at different times, since minimum temporary +// storage is proportional to the output size rather than the inputs size. // -// value: How many output channels to produce per time slice. -// If not specified, defaults to 13 -func MfccDctCoefficientCount(value int64) MfccAttr { - return func(m optionalAttr) { - m["dct_coefficient_count"] = value - } -} - -// Transforms a spectrogram into a form that's useful for speech recognition. +// Unlike the original `accumulate_n`, `accumulate_n_v2` is differentiable. // -// Mel Frequency Cepstral Coefficients are a way of representing audio data that's -// been effective as an input feature for machine learning. They are created by -// taking the spectrum of a spectrogram (a 'cepstrum'), and discarding some of the -// higher frequencies that are less significant to the human ear. They have a long -// history in the speech recognition world, and https://en.wikipedia.org/wiki/Mel-frequency_cepstrum -// is a good resource to learn more. +// Returns a `Tensor` of same shape and type as the elements of `inputs`. // // Arguments: -// spectrogram: Typically produced by the Spectrogram op, with magnitude_squared -// set to true. -// sample_rate: How many samples per second the source audio used. -func Mfcc(scope *Scope, spectrogram tf.Output, sample_rate tf.Output, optional ...MfccAttr) (output tf.Output) { +// inputs: A list of `Tensor` objects, each with same shape and type. +// shape: Shape of elements of `inputs`. +func AccumulateNV2(scope *Scope, inputs []tf.Output, shape tf.Shape) (sum tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } + attrs := map[string]interface{}{"shape": shape} opspec := tf.OpSpec{ - Type: "Mfcc", + Type: "AccumulateNV2", Input: []tf.Input{ - spectrogram, sample_rate, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Returns the element-wise sum of a list of tensors. -// -// `tf.accumulate_n_v2` performs the same operation as `tf.add_n`, but does not -// wait for all of its inputs to be ready before beginning to sum. This can -// save memory if inputs are ready at different times, since minimum temporary -// storage is proportional to the output size rather than the inputs size. -// -// Unlike the original `accumulate_n`, `accumulate_n_v2` is differentiable. -// -// Returns a `Tensor` of same shape and type as the elements of `inputs`. -// -// Arguments: -// inputs: A list of `Tensor` objects, each with same shape and type. -// shape: Shape of elements of `inputs`. -func AccumulateNV2(scope *Scope, inputs []tf.Output, shape tf.Shape) (sum tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"shape": shape} - opspec := tf.OpSpec{ - Type: "AccumulateNV2", - Input: []tf.Input{ - tf.OutputList(inputs), + tf.OutputList(inputs), }, Attrs: attrs, } @@ -17022,87 +17224,129 @@ func MatrixBandPart(scope *Scope, input tf.Output, num_lower tf.Output, num_uppe return op.Output(0) } -// Computes acos of x element-wise. -func Acos(scope *Scope, x tf.Output) (y tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Acos", - Input: []tf.Input{ - x, - }, +// QuantizedMatMulAttr is an optional argument to QuantizedMatMul. +type QuantizedMatMulAttr func(optionalAttr) + +// QuantizedMatMulToutput sets the optional Toutput attribute to value. +// If not specified, defaults to DT_QINT32 +func QuantizedMatMulToutput(value tf.DataType) QuantizedMatMulAttr { + return func(m optionalAttr) { + m["Toutput"] = value } - op := scope.AddOperation(opspec) - return op.Output(0) } -// MaxPoolWithArgmaxAttr is an optional argument to MaxPoolWithArgmax. -type MaxPoolWithArgmaxAttr func(optionalAttr) +// QuantizedMatMulTransposeA sets the optional transpose_a attribute to value. +// +// value: If true, `a` is transposed before multiplication. +// If not specified, defaults to false +func QuantizedMatMulTransposeA(value bool) QuantizedMatMulAttr { + return func(m optionalAttr) { + m["transpose_a"] = value + } +} -// MaxPoolWithArgmaxTargmax sets the optional Targmax attribute to value. -// If not specified, defaults to DT_INT64 -func MaxPoolWithArgmaxTargmax(value tf.DataType) MaxPoolWithArgmaxAttr { +// QuantizedMatMulTransposeB sets the optional transpose_b attribute to value. +// +// value: If true, `b` is transposed before multiplication. +// If not specified, defaults to false +func QuantizedMatMulTransposeB(value bool) QuantizedMatMulAttr { return func(m optionalAttr) { - m["Targmax"] = value + m["transpose_b"] = value } } -// Performs max pooling on the input and outputs both max values and indices. +// QuantizedMatMulTactivation sets the optional Tactivation attribute to value. // -// The indices in `argmax` are flattened, so that a maximum value at position -// `[b, y, x, c]` becomes flattened index -// `((b * height + y) * width + x) * channels + c`. +// value: The type of output produced by activation function +// following this operation. +// If not specified, defaults to DT_QUINT8 +func QuantizedMatMulTactivation(value tf.DataType) QuantizedMatMulAttr { + return func(m optionalAttr) { + m["Tactivation"] = value + } +} + +// Perform a quantized matrix multiplication of `a` by the matrix `b`. // -// The indices returned are always in `[0, height) x [0, width)` before flattening, -// even if padding is involved and the mathematically correct answer is outside -// (either negative or too large). This is a bug, but fixing it is difficult to do -// in a safe backwards compatible way, especially due to flattening. +// The inputs must be two-dimensional matrices and the inner dimension of +// `a` (after being transposed if `transpose_a` is non-zero) must match the +// outer dimension of `b` (after being transposed if `transposed_b` is +// non-zero). // // Arguments: -// input: 4-D with shape `[batch, height, width, channels]`. Input to pool over. -// ksize: The size of the window for each dimension of the input tensor. -// strides: The stride of the sliding window for each dimension of the -// input tensor. -// padding: The type of padding algorithm to use. +// a: Must be a two-dimensional tensor. +// b: Must be a two-dimensional tensor. +// min_a: The float value that the lowest quantized `a` value represents. +// max_a: The float value that the highest quantized `a` value represents. +// min_b: The float value that the lowest quantized `b` value represents. +// max_b: The float value that the highest quantized `b` value represents. // -// Returns The max pooled output tensor.4-D. The flattened indices of the max values chosen for each output. -func MaxPoolWithArgmax(scope *Scope, input tf.Output, ksize []int64, strides []int64, padding string, optional ...MaxPoolWithArgmaxAttr) (output tf.Output, argmax tf.Output) { +// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. +func QuantizedMatMul(scope *Scope, a tf.Output, b tf.Output, min_a tf.Output, max_a tf.Output, min_b tf.Output, max_b tf.Output, optional ...QuantizedMatMulAttr) (out tf.Output, min_out tf.Output, max_out tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"ksize": ksize, "strides": strides, "padding": padding} + attrs := map[string]interface{}{} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "MaxPoolWithArgmax", + Type: "QuantizedMatMul", Input: []tf.Input{ - input, + a, b, min_a, max_a, min_b, max_b, }, Attrs: attrs, } op := scope.AddOperation(opspec) - return op.Output(0), op.Output(1) + return op.Output(0), op.Output(1), op.Output(2) } -// Transforms a serialized tensorflow.TensorProto proto into a Tensor. +// Does nothing. Serves as a control trigger for scheduling. // -// Arguments: -// serialized: A scalar string containing a serialized TensorProto proto. -// out_type: The type of the serialized tensor. The provided type must match the -// type of the serialized tensor and no implicit conversion will take place. +// Only useful as a placeholder for control edges. // -// Returns A Tensor of type `out_type`. -func ParseTensor(scope *Scope, serialized tf.Output, out_type tf.DataType) (output tf.Output) { +// Returns the created operation. +func ControlTrigger(scope *Scope) (o *tf.Operation) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"out_type": out_type} opspec := tf.OpSpec{ - Type: "ParseTensor", + Type: "ControlTrigger", + } + return scope.AddOperation(opspec) +} + +// Batch normalization. +// +// DEPRECATED at GraphDef version 9: Use tf.nn.batch_normalization() +// +// This op is deprecated. Prefer `tf.nn.batch_normalization`. +// +// Arguments: +// t: A 4D input Tensor. +// m: A 1D mean Tensor with size matching the last dimension of t. +// This is the first output from tf.nn.moments, +// or a saved moving average thereof. +// v: A 1D variance Tensor with size matching the last dimension of t. +// This is the second output from tf.nn.moments, +// or a saved moving average thereof. +// beta: A 1D beta Tensor with size matching the last dimension of t. +// An offset to be added to the normalized tensor. +// gamma: A 1D gamma Tensor with size matching the last dimension of t. +// If "scale_after_normalization" is true, this tensor will be multiplied +// with the normalized tensor. +// variance_epsilon: A small float number to avoid dividing by 0. +// scale_after_normalization: A bool indicating whether the resulted tensor +// needs to be multiplied with gamma. +func BatchNormWithGlobalNormalization(scope *Scope, t tf.Output, m tf.Output, v tf.Output, beta tf.Output, gamma tf.Output, variance_epsilon float32, scale_after_normalization bool) (result tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"variance_epsilon": variance_epsilon, "scale_after_normalization": scale_after_normalization} + opspec := tf.OpSpec{ + Type: "BatchNormWithGlobalNormalization", Input: []tf.Input{ - serialized, + t, m, v, beta, gamma, }, Attrs: attrs, } @@ -17110,113 +17354,95 @@ func ParseTensor(scope *Scope, serialized tf.Output, out_type tf.DataType) (outp return op.Output(0) } -// MapClearAttr is an optional argument to MapClear. -type MapClearAttr func(optionalAttr) - -// MapClearCapacity sets the optional capacity attribute to value. -// If not specified, defaults to 0 +// Deprecated. Use TensorArrayReadV3 // -// REQUIRES: value >= 0 -func MapClearCapacity(value int64) MapClearAttr { - return func(m optionalAttr) { - m["capacity"] = value +// DEPRECATED at GraphDef version 26: Use TensorArrayReadV3 +func TensorArrayReadV2(scope *Scope, handle tf.Output, index tf.Output, flow_in tf.Output, dtype tf.DataType) (value tf.Output) { + if scope.Err() != nil { + return } -} - -// MapClearMemoryLimit sets the optional memory_limit attribute to value. -// If not specified, defaults to 0 -// -// REQUIRES: value >= 0 -func MapClearMemoryLimit(value int64) MapClearAttr { - return func(m optionalAttr) { - m["memory_limit"] = value + attrs := map[string]interface{}{"dtype": dtype} + opspec := tf.OpSpec{ + Type: "TensorArrayReadV2", + Input: []tf.Input{ + handle, index, flow_in, + }, + Attrs: attrs, } + op := scope.AddOperation(opspec) + return op.Output(0) } -// MapClearContainer sets the optional container attribute to value. -// If not specified, defaults to "" -func MapClearContainer(value string) MapClearAttr { - return func(m optionalAttr) { - m["container"] = value - } -} +// QuantizedMulAttr is an optional argument to QuantizedMul. +type QuantizedMulAttr func(optionalAttr) -// MapClearSharedName sets the optional shared_name attribute to value. -// If not specified, defaults to "" -func MapClearSharedName(value string) MapClearAttr { +// QuantizedMulToutput sets the optional Toutput attribute to value. +// If not specified, defaults to DT_QINT32 +func QuantizedMulToutput(value tf.DataType) QuantizedMulAttr { return func(m optionalAttr) { - m["shared_name"] = value + m["Toutput"] = value } } -// Op removes all elements in the underlying container. +// Returns x * y element-wise, working on quantized buffers. // -// Returns the created operation. -func MapClear(scope *Scope, dtypes []tf.DataType, optional ...MapClearAttr) (o *tf.Operation) { +// Arguments: +// +// +// min_x: The float value that the lowest quantized `x` value represents. +// max_x: The float value that the highest quantized `x` value represents. +// min_y: The float value that the lowest quantized `y` value represents. +// max_y: The float value that the highest quantized `y` value represents. +// +// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. +// +// *NOTE*: `QuantizedMul` supports limited forms of broadcasting. More about +// broadcasting [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +func QuantizedMul(scope *Scope, x tf.Output, y tf.Output, min_x tf.Output, max_x tf.Output, min_y tf.Output, max_y tf.Output, optional ...QuantizedMulAttr) (z tf.Output, min_z tf.Output, max_z tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{"dtypes": dtypes} + attrs := map[string]interface{}{} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "MapClear", - + Type: "QuantizedMul", + Input: []tf.Input{ + x, y, min_x, max_x, min_y, max_y, + }, Attrs: attrs, } - return scope.AddOperation(opspec) + op := scope.AddOperation(opspec) + return op.Output(0), op.Output(1), op.Output(2) } -// DecodeCSVAttr is an optional argument to DecodeCSV. -type DecodeCSVAttr func(optionalAttr) +// QuantizedAddAttr is an optional argument to QuantizedAdd. +type QuantizedAddAttr func(optionalAttr) -// DecodeCSVFieldDelim sets the optional field_delim attribute to value. -// -// value: char delimiter to separate fields in a record. -// If not specified, defaults to "," -func DecodeCSVFieldDelim(value string) DecodeCSVAttr { +// QuantizedAddToutput sets the optional Toutput attribute to value. +// If not specified, defaults to DT_QINT32 +func QuantizedAddToutput(value tf.DataType) QuantizedAddAttr { return func(m optionalAttr) { - m["field_delim"] = value + m["Toutput"] = value } } -// DecodeCSVUseQuoteDelim sets the optional use_quote_delim attribute to value. +// Returns x + y element-wise, working on quantized buffers. // -// value: If false, treats double quotation marks as regular -// characters inside of the string fields (ignoring RFC 4180, Section 2, -// Bullet 5). -// If not specified, defaults to true -func DecodeCSVUseQuoteDelim(value bool) DecodeCSVAttr { - return func(m optionalAttr) { - m["use_quote_delim"] = value - } -} - -// DecodeCSVNaValue sets the optional na_value attribute to value. +// Arguments: // -// value: Additional string to recognize as NA/NaN. -// If not specified, defaults to "" -func DecodeCSVNaValue(value string) DecodeCSVAttr { - return func(m optionalAttr) { - m["na_value"] = value - } -} - -// Convert CSV records to tensors. Each column maps to one tensor. // -// RFC 4180 format is expected for the CSV records. -// (https://tools.ietf.org/html/rfc4180) -// Note that we allow leading and trailing spaces with int or float field. +// min_x: The float value that the lowest quantized `x` value represents. +// max_x: The float value that the highest quantized `x` value represents. +// min_y: The float value that the lowest quantized `y` value represents. +// max_y: The float value that the highest quantized `y` value represents. // -// Arguments: -// records: Each string is a record/row in the csv and all records should have -// the same format. -// record_defaults: One tensor per column of the input record, with either a -// scalar default value for that column or empty if the column is required. +// Returns The float value that the lowest quantized output value represents.The float value that the highest quantized output value represents. // -// Returns Each tensor will have the same shape as records. -func DecodeCSV(scope *Scope, records tf.Output, record_defaults []tf.Output, optional ...DecodeCSVAttr) (output []tf.Output) { +// *NOTE*: `QuantizedAdd` supports limited forms of broadcasting. More about +// broadcasting [here](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) +func QuantizedAdd(scope *Scope, x tf.Output, y tf.Output, min_x tf.Output, max_x tf.Output, min_y tf.Output, max_y tf.Output, optional ...QuantizedAddAttr) (z tf.Output, min_z tf.Output, max_z tf.Output) { if scope.Err() != nil { return } @@ -17225,84 +17451,117 @@ func DecodeCSV(scope *Scope, records tf.Output, record_defaults []tf.Output, opt a(attrs) } opspec := tf.OpSpec{ - Type: "DecodeCSV", + Type: "QuantizedAdd", Input: []tf.Input{ - records, tf.OutputList(record_defaults), + x, y, min_x, max_x, min_y, max_y, }, Attrs: attrs, } op := scope.AddOperation(opspec) - if scope.Err() != nil { - return + return op.Output(0), op.Output(1), op.Output(2) +} + +// MfccAttr is an optional argument to Mfcc. +type MfccAttr func(optionalAttr) + +// MfccUpperFrequencyLimit sets the optional upper_frequency_limit attribute to value. +// +// value: The highest frequency to use when calculating the +// ceptstrum. +// If not specified, defaults to 4000 +func MfccUpperFrequencyLimit(value float32) MfccAttr { + return func(m optionalAttr) { + m["upper_frequency_limit"] = value } - var idx int - var err error - if output, idx, err = makeOutputList(op, idx, "output"); err != nil { - scope.UpdateErr("DecodeCSV", err) - return +} + +// MfccLowerFrequencyLimit sets the optional lower_frequency_limit attribute to value. +// +// value: The lowest frequency to use when calculating the +// ceptstrum. +// If not specified, defaults to 20 +func MfccLowerFrequencyLimit(value float32) MfccAttr { + return func(m optionalAttr) { + m["lower_frequency_limit"] = value + } +} + +// MfccFilterbankChannelCount sets the optional filterbank_channel_count attribute to value. +// +// value: Resolution of the Mel bank used internally. +// If not specified, defaults to 40 +func MfccFilterbankChannelCount(value int64) MfccAttr { + return func(m optionalAttr) { + m["filterbank_channel_count"] = value } - return output } -// Returns the rank of a tensor. -// -// This operation returns an integer representing the rank of `input`. +// MfccDctCoefficientCount sets the optional dct_coefficient_count attribute to value. // -// For example: +// value: How many output channels to produce per time slice. +// If not specified, defaults to 13 +func MfccDctCoefficientCount(value int64) MfccAttr { + return func(m optionalAttr) { + m["dct_coefficient_count"] = value + } +} + +// Transforms a spectrogram into a form that's useful for speech recognition. // -// ``` -// # 't' is [[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]] -// # shape of tensor 't' is [2, 2, 3] -// rank(t) ==> 3 -// ``` +// Mel Frequency Cepstral Coefficients are a way of representing audio data that's +// been effective as an input feature for machine learning. They are created by +// taking the spectrum of a spectrogram (a 'cepstrum'), and discarding some of the +// higher frequencies that are less significant to the human ear. They have a long +// history in the speech recognition world, and https://en.wikipedia.org/wiki/Mel-frequency_cepstrum +// is a good resource to learn more. // -// **Note**: The rank of a tensor is not the same as the rank of a matrix. The rank -// of a tensor is the number of indices required to uniquely select each element -// of the tensor. Rank is also known as "order", "degree", or "ndims." -func Rank(scope *Scope, input tf.Output) (output tf.Output) { +// Arguments: +// spectrogram: Typically produced by the Spectrogram op, with magnitude_squared +// set to true. +// sample_rate: How many samples per second the source audio used. +func Mfcc(scope *Scope, spectrogram tf.Output, sample_rate tf.Output, optional ...MfccAttr) (output tf.Output) { if scope.Err() != nil { return } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } opspec := tf.OpSpec{ - Type: "Rank", + Type: "Mfcc", Input: []tf.Input{ - input, + spectrogram, sample_rate, }, + Attrs: attrs, } op := scope.AddOperation(opspec) return op.Output(0) } -// Output a fact about factorials. -func Fact(scope *Scope) (fact tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "Fact", - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Makes its input available to the next iteration. +// Given a quantized tensor described by (input, input_min, input_max), outputs a +// +// range that covers the actual values present in that tensor. This op is +// typically used to produce the requested_output_min and requested_output_max for +// Requantize. // // Arguments: -// data: The tensor to be made available to the next iteration. // -// Returns The same tensor as `data`. -func NextIteration(scope *Scope, data tf.Output) (output tf.Output) { +// input_min: The float value that the minimum quantized input value represents. +// input_max: The float value that the maximum quantized input value represents. +// +// Returns The computed min output.the computed max output. +func RequantizationRange(scope *Scope, input tf.Output, input_min tf.Output, input_max tf.Output) (output_min tf.Output, output_max tf.Output) { if scope.Err() != nil { return } opspec := tf.OpSpec{ - Type: "NextIteration", + Type: "RequantizationRange", Input: []tf.Input{ - data, + input, input_min, input_max, }, } op := scope.AddOperation(opspec) - return op.Output(0) + return op.Output(0), op.Output(1) } // MapPeekAttr is an optional argument to MapPeek. @@ -18911,101 +19170,6 @@ func AdjustSaturation(scope *Scope, images tf.Output, scale tf.Output) (output t return op.Output(0) } -// Elementwise computes the bitwise OR of `x` and `y`. -// -// The result will have those bits set, that are set in `x`, `y` or both. The -// computation is performed on the underlying representations of `x` and `y`. -func BitwiseOr(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "BitwiseOr", - Input: []tf.Input{ - x, y, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// MatrixSolveLsAttr is an optional argument to MatrixSolveLs. -type MatrixSolveLsAttr func(optionalAttr) - -// MatrixSolveLsFast sets the optional fast attribute to value. -// If not specified, defaults to true -func MatrixSolveLsFast(value bool) MatrixSolveLsAttr { - return func(m optionalAttr) { - m["fast"] = value - } -} - -// Solves one or more linear least-squares problems. -// -// `matrix` is a tensor of shape `[..., M, N]` whose inner-most 2 dimensions -// form real or complex matrices of size `[M, N]`. `Rhs` is a tensor of the same -// type as `matrix` and shape `[..., M, K]`. -// The output is a tensor shape `[..., N, K]` where each output matrix solves -// each of the equations -// `matrix[..., :, :]` * `output[..., :, :]` = `rhs[..., :, :]` -// in the least squares sense. -// -// We use the following notation for (complex) matrix and right-hand sides -// in the batch: -// -// `matrix`=\\(A \in \mathbb{C}^{m \times n}\\), -// `rhs`=\\(B \in \mathbb{C}^{m \times k}\\), -// `output`=\\(X \in \mathbb{C}^{n \times k}\\), -// `l2_regularizer`=\\(\lambda \in \mathbb{R}\\). -// -// If `fast` is `True`, then the solution is computed by solving the normal -// equations using Cholesky decomposition. Specifically, if \\(m \ge n\\) then -// \\(X = (A^H A + \lambda I)^{-1} A^H B\\), which solves the least-squares -// problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + -// \lambda ||Z||_F^2\\). If \\(m \lt n\\) then `output` is computed as -// \\(X = A^H (A A^H + \lambda I)^{-1} B\\), which (for \\(\lambda = 0\\)) is the -// minimum-norm solution to the under-determined linear system, i.e. -// \\(X = \mathrm{argmin}_{Z \in \mathbb{C}^{n \times k} } ||Z||_F^2 \\), -// subject to \\(A Z = B\\). Notice that the fast path is only numerically stable -// when \\(A\\) is numerically full rank and has a condition number -// \\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or\\(\lambda\\) is -// sufficiently large. -// -// If `fast` is `False` an algorithm based on the numerically robust complete -// orthogonal decomposition is used. This computes the minimum-norm -// least-squares solution, even when \\(A\\) is rank deficient. This path is -// typically 6-7 times slower than the fast path. If `fast` is `False` then -// `l2_regularizer` is ignored. -// -// Arguments: -// matrix: Shape is `[..., M, N]`. -// rhs: Shape is `[..., M, K]`. -// l2_regularizer: Scalar tensor. -// -// @compatibility(numpy) -// Equivalent to np.linalg.lstsq -// @end_compatibility -// -// Returns Shape is `[..., N, K]`. -func MatrixSolveLs(scope *Scope, matrix tf.Output, rhs tf.Output, l2_regularizer tf.Output, optional ...MatrixSolveLsAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "MatrixSolveLs", - Input: []tf.Input{ - matrix, rhs, l2_regularizer, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // SvdAttr is an optional argument to Svd. type SvdAttr func(optionalAttr) @@ -20803,28 +20967,101 @@ func ExtractGlimpseUniformNoise(value bool) ExtractGlimpseAttr { // * If the coordinates are not normalized they are interpreted as // numbers of pixels. // -// Arguments: -// input: A 4-D float tensor of shape `[batch_size, height, width, channels]`. -// size: A 1-D tensor of 2 elements containing the size of the glimpses -// to extract. The glimpse height must be specified first, following -// by the glimpse width. -// offsets: A 2-D integer tensor of shape `[batch_size, 2]` containing -// the y, x locations of the center of each window. +// Arguments: +// input: A 4-D float tensor of shape `[batch_size, height, width, channels]`. +// size: A 1-D tensor of 2 elements containing the size of the glimpses +// to extract. The glimpse height must be specified first, following +// by the glimpse width. +// offsets: A 2-D integer tensor of shape `[batch_size, 2]` containing +// the y, x locations of the center of each window. +// +// Returns A tensor representing the glimpses `[batch_size, +// glimpse_height, glimpse_width, channels]`. +func ExtractGlimpse(scope *Scope, input tf.Output, size tf.Output, offsets tf.Output, optional ...ExtractGlimpseAttr) (glimpse tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{} + for _, a := range optional { + a(attrs) + } + opspec := tf.OpSpec{ + Type: "ExtractGlimpse", + Input: []tf.Input{ + input, size, offsets, + }, + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// A container for an iterator resource. +// +// Returns A handle to the iterator that can be passed to a "MakeIterator" +// or "IteratorGetNext" op. +func Iterator(scope *Scope, shared_name string, container string, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { + if scope.Err() != nil { + return + } + attrs := map[string]interface{}{"shared_name": shared_name, "container": container, "output_types": output_types, "output_shapes": output_shapes} + opspec := tf.OpSpec{ + Type: "Iterator", + + Attrs: attrs, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + +// CropAndResizeGradImageAttr is an optional argument to CropAndResizeGradImage. +type CropAndResizeGradImageAttr func(optionalAttr) + +// CropAndResizeGradImageMethod sets the optional method attribute to value. +// +// value: A string specifying the interpolation method. Only 'bilinear' is +// supported for now. +// If not specified, defaults to "bilinear" +func CropAndResizeGradImageMethod(value string) CropAndResizeGradImageAttr { + return func(m optionalAttr) { + m["method"] = value + } +} + +// Computes the gradient of the crop_and_resize op wrt the input image tensor. +// +// Arguments: +// grads: A 4-D tensor of shape `[num_boxes, crop_height, crop_width, depth]`. +// boxes: A 2-D tensor of shape `[num_boxes, 4]`. The `i`-th row of the tensor +// specifies the coordinates of a box in the `box_ind[i]` image and is specified +// in normalized coordinates `[y1, x1, y2, x2]`. A normalized coordinate value of +// `y` is mapped to the image coordinate at `y * (image_height - 1)`, so as the +// `[0, 1]` interval of normalized image height is mapped to +// `[0, image_height - 1] in image height coordinates. We do allow y1 > y2, in +// which case the sampled crop is an up-down flipped version of the original +// image. The width dimension is treated similarly. Normalized coordinates +// outside the `[0, 1]` range are allowed, in which case we use +// `extrapolation_value` to extrapolate the input image values. +// box_ind: A 1-D tensor of shape `[num_boxes]` with int32 values in `[0, batch)`. +// The value of `box_ind[i]` specifies the image that the `i`-th box refers to. +// image_size: A 1-D tensor with value `[batch, image_height, image_width, depth]` +// containing the original image size. Both `image_height` and `image_width` need +// to be positive. +// // -// Returns A tensor representing the glimpses `[batch_size, -// glimpse_height, glimpse_width, channels]`. -func ExtractGlimpse(scope *Scope, input tf.Output, size tf.Output, offsets tf.Output, optional ...ExtractGlimpseAttr) (glimpse tf.Output) { +// Returns A 4-D tensor of shape `[batch, image_height, image_width, depth]`. +func CropAndResizeGradImage(scope *Scope, grads tf.Output, boxes tf.Output, box_ind tf.Output, image_size tf.Output, T tf.DataType, optional ...CropAndResizeGradImageAttr) (output tf.Output) { if scope.Err() != nil { return } - attrs := map[string]interface{}{} + attrs := map[string]interface{}{"T": T} for _, a := range optional { a(attrs) } opspec := tf.OpSpec{ - Type: "ExtractGlimpse", + Type: "CropAndResizeGradImage", Input: []tf.Input{ - input, size, offsets, + grads, boxes, box_ind, image_size, }, Attrs: attrs, } @@ -20832,24 +21069,6 @@ func ExtractGlimpse(scope *Scope, input tf.Output, size tf.Output, offsets tf.Ou return op.Output(0) } -// A container for an iterator resource. -// -// Returns A handle to the iterator that can be passed to a "MakeIterator" -// or "IteratorGetNext" op. -func Iterator(scope *Scope, shared_name string, container string, output_types []tf.DataType, output_shapes []tf.Shape) (handle tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"shared_name": shared_name, "container": container, "output_types": output_types, "output_shapes": output_shapes} - opspec := tf.OpSpec{ - Type: "Iterator", - - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // ShuffleDatasetAttr is an optional argument to ShuffleDataset. type ShuffleDatasetAttr func(optionalAttr) @@ -21717,47 +21936,6 @@ func CacheDataset(scope *Scope, input_dataset tf.Output, filename tf.Output, out return op.Output(0) } -// PlaceholderAttr is an optional argument to Placeholder. -type PlaceholderAttr func(optionalAttr) - -// PlaceholderShape sets the optional shape attribute to value. -// -// value: (Optional) The shape of the tensor. If the shape has 0 dimensions, the -// shape is unconstrained. -// If not specified, defaults to -func PlaceholderShape(value tf.Shape) PlaceholderAttr { - return func(m optionalAttr) { - m["shape"] = value - } -} - -// A placeholder op for a value that will be fed into the computation. -// -// N.B. This operation will fail with an error if it is executed. It is -// intended as a way to represent a value that will always be fed, and to -// provide attrs that enable the fed value to be checked at runtime. -// -// Arguments: -// dtype: The type of elements in the tensor. -// -// Returns A placeholder tensor that must be replaced using the feed mechanism. -func Placeholder(scope *Scope, dtype tf.DataType, optional ...PlaceholderAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"dtype": dtype} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "Placeholder", - - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Creates a dataset that executes a SQL query and emits rows of the result set. // // Arguments: @@ -23339,101 +23517,6 @@ func TensorArrayCloseV2(scope *Scope, handle tf.Output) (o *tf.Operation) { return scope.AddOperation(opspec) } -// CropAndResizeGradImageAttr is an optional argument to CropAndResizeGradImage. -type CropAndResizeGradImageAttr func(optionalAttr) - -// CropAndResizeGradImageMethod sets the optional method attribute to value. -// -// value: A string specifying the interpolation method. Only 'bilinear' is -// supported for now. -// If not specified, defaults to "bilinear" -func CropAndResizeGradImageMethod(value string) CropAndResizeGradImageAttr { - return func(m optionalAttr) { - m["method"] = value - } -} - -// Computes the gradient of the crop_and_resize op wrt the input image tensor. -// -// Arguments: -// grads: A 4-D tensor of shape `[num_boxes, crop_height, crop_width, depth]`. -// boxes: A 2-D tensor of shape `[num_boxes, 4]`. The `i`-th row of the tensor -// specifies the coordinates of a box in the `box_ind[i]` image and is specified -// in normalized coordinates `[y1, x1, y2, x2]`. A normalized coordinate value of -// `y` is mapped to the image coordinate at `y * (image_height - 1)`, so as the -// `[0, 1]` interval of normalized image height is mapped to -// `[0, image_height - 1] in image height coordinates. We do allow y1 > y2, in -// which case the sampled crop is an up-down flipped version of the original -// image. The width dimension is treated similarly. Normalized coordinates -// outside the `[0, 1]` range are allowed, in which case we use -// `extrapolation_value` to extrapolate the input image values. -// box_ind: A 1-D tensor of shape `[num_boxes]` with int32 values in `[0, batch)`. -// The value of `box_ind[i]` specifies the image that the `i`-th box refers to. -// image_size: A 1-D tensor with value `[batch, image_height, image_width, depth]` -// containing the original image size. Both `image_height` and `image_width` need -// to be positive. -// -// -// Returns A 4-D tensor of shape `[batch, image_height, image_width, depth]`. -func CropAndResizeGradImage(scope *Scope, grads tf.Output, boxes tf.Output, box_ind tf.Output, image_size tf.Output, T tf.DataType, optional ...CropAndResizeGradImageAttr) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"T": T} - for _, a := range optional { - a(attrs) - } - opspec := tf.OpSpec{ - Type: "CropAndResizeGradImage", - Input: []tf.Input{ - grads, boxes, box_ind, image_size, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Reads and outputs the entire contents of the input filename. -func ReadFile(scope *Scope, filename tf.Output) (contents tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "ReadFile", - Input: []tf.Input{ - filename, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// Concatenates tensors along one dimension. -// -// Arguments: -// values: List of `N` Tensors to concatenate. Their ranks and types must match, -// and their sizes must match in all dimensions except `concat_dim`. -// axis: 0-D. The dimension along which to concatenate. Must be in the -// range [-rank(values), rank(values)). -// -// Returns A `Tensor` with the concatenation of values stacked along the -// `concat_dim` dimension. This tensor's shape matches that of `values` except -// in `concat_dim` where it has the sum of the sizes. -func ConcatV2(scope *Scope, values []tf.Output, axis tf.Output) (output tf.Output) { - if scope.Err() != nil { - return - } - opspec := tf.OpSpec{ - Type: "ConcatV2", - Input: []tf.Input{ - tf.OutputList(values), axis, - }, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - // Forwards the value of an available tensor from `inputs` to `output`. // // `Merge` waits for at least one of the tensors in `inputs` to become available. @@ -27804,86 +27887,3 @@ func BroadcastGradientArgs(scope *Scope, s0 tf.Output, s1 tf.Output) (r0 tf.Outp op := scope.AddOperation(opspec) return op.Output(0), op.Output(1) } - -// Pads a tensor with mirrored values. -// -// This operation pads a `input` with mirrored values according to the `paddings` -// you specify. `paddings` is an integer tensor with shape `[n, 2]`, where n is -// the rank of `input`. For each dimension D of `input`, `paddings[D, 0]` indicates -// how many values to add before the contents of `input` in that dimension, and -// `paddings[D, 1]` indicates how many values to add after the contents of `input` -// in that dimension. Both `paddings[D, 0]` and `paddings[D, 1]` must be no greater -// than `input.dim_size(D)` (or `input.dim_size(D) - 1`) if `copy_border` is true -// (if false, respectively). -// -// The padded size of each dimension D of the output is: -// -// `paddings(D, 0) + input.dim_size(D) + paddings(D, 1)` -// -// For example: -// -// ``` -// # 't' is [[1, 2, 3], [4, 5, 6]]. -// # 'paddings' is [[1, 1]], [2, 2]]. -// # 'mode' is SYMMETRIC. -// # rank of 't' is 2. -// pad(t, paddings) ==> [[2, 1, 1, 2, 3, 3, 2] -// [2, 1, 1, 2, 3, 3, 2] -// [5, 4, 4, 5, 6, 6, 5] -// [5, 4, 4, 5, 6, 6, 5]] -// ``` -// -// Arguments: -// input: The input tensor to be padded. -// paddings: A two-column matrix specifying the padding sizes. The number of -// rows must be the same as the rank of `input`. -// mode: Either `REFLECT` or `SYMMETRIC`. In reflect mode the padded regions -// do not include the borders, while in symmetric mode the padded regions -// do include the borders. For example, if `input` is `[1, 2, 3]` and `paddings` -// is `[0, 2]`, then the output is `[1, 2, 3, 2, 1]` in reflect mode, and -// it is `[1, 2, 3, 3, 2]` in symmetric mode. -// -// Returns The padded tensor. -func MirrorPad(scope *Scope, input tf.Output, paddings tf.Output, mode string) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"mode": mode} - opspec := tf.OpSpec{ - Type: "MirrorPad", - Input: []tf.Input{ - input, paddings, - }, - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} - -// A placeholder op for a value that will be fed into the computation. -// -// DEPRECATED at GraphDef version 23: Placeholder now behaves the same as PlaceholderV2. -// -// N.B. This operation will fail with an error if it is executed. It is -// intended as a way to represent a value that will always be fed, and to -// provide attrs that enable the fed value to be checked at runtime. -// -// Arguments: -// dtype: The type of elements in the tensor. -// shape: The shape of the tensor. The shape can be any partially-specified -// shape. To be unconstrained, pass in a shape with unknown rank. -// -// Returns A placeholder tensor that must be replaced using the feed mechanism. -func PlaceholderV2(scope *Scope, dtype tf.DataType, shape tf.Shape) (output tf.Output) { - if scope.Err() != nil { - return - } - attrs := map[string]interface{}{"dtype": dtype, "shape": shape} - opspec := tf.OpSpec{ - Type: "PlaceholderV2", - - Attrs: attrs, - } - op := scope.AddOperation(opspec) - return op.Output(0) -} -- GitLab From e9d6c89aaaf65db2dbaacacdfecdee4a56a3cb7d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 18:39:29 -0700 Subject: [PATCH 187/906] Switch Android C++ compilation mode to "-std=c++11". PiperOrigin-RevId: 190306256 --- tensorflow/tools/ci_build/builds/android.sh | 3 ++- tensorflow/tools/ci_build/builds/android_full.sh | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tensorflow/tools/ci_build/builds/android.sh b/tensorflow/tools/ci_build/builds/android.sh index 564c5aa148..d81793efe0 100755 --- a/tensorflow/tools/ci_build/builds/android.sh +++ b/tensorflow/tools/ci_build/builds/android.sh @@ -29,7 +29,8 @@ echo "========== TensorFlow Demo Build Test ==========" # Enable sandboxing so that zip archives don't get incorrectly packaged # in assets/ dir (see https://github.com/bazelbuild/bazel/issues/2334) # TODO(gunan): remove extra flags once sandboxing is enabled for all builds. -bazel --bazelrc=/dev/null build -c opt --fat_apk_cpu=x86_64 \ +bazel --bazelrc=/dev/null build \ + --compilation_mode=opt --cxxopt=-std=c++11 --fat_apk_cpu=x86_64 \ --spawn_strategy=sandboxed --genrule_strategy=sandboxed \ //tensorflow/examples/android:tensorflow_demo diff --git a/tensorflow/tools/ci_build/builds/android_full.sh b/tensorflow/tools/ci_build/builds/android_full.sh index 9d449241e8..41dc66dd54 100755 --- a/tensorflow/tools/ci_build/builds/android_full.sh +++ b/tensorflow/tools/ci_build/builds/android_full.sh @@ -40,7 +40,8 @@ rm -rf ${AAR_LIB_TMP} for CPU in ${CPUS//,/ } do echo "========== Building native libs for Android ${CPU} ==========" - bazel build -c opt --config=monolithic --cpu=${CPU} \ + bazel build --config=monolithic --cpu=${CPU} \ + --compilation_mode=opt --cxxopt=-std=c++11 \ --crosstool_top=//external:android/crosstool \ --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ //tensorflow/core:android_tensorflow_lib \ @@ -62,7 +63,8 @@ done # in assets/ dir (see https://github.com/bazelbuild/bazel/issues/2334) # TODO(gunan): remove extra flags once sandboxing is enabled for all builds. echo "========== Building TensorFlow Android Jar and Demo ==========" -bazel --bazelrc=/dev/null build -c opt --config=monolithic --fat_apk_cpu=${CPUS} \ +bazel --bazelrc=/dev/null build --config=monolithic --fat_apk_cpu=${CPUS} \ + --compilation_mode=opt --cxxopt=-std=c++11 \ --spawn_strategy=sandboxed --genrule_strategy=sandboxed \ //tensorflow/contrib/android:android_tensorflow_inference_java \ //tensorflow/contrib/android:android_tensorflow_inference_java.aar \ -- GitLab From f3f58f4486731faf4137fa62cdf1f885dccfc95b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 18:44:35 -0700 Subject: [PATCH 188/906] When import_scoped_meta_graph is called within a name scope, but called without an import_scope, the names of the created variables are wrong, resulting in key not found errors when adding these variables to their corresponding collections. PiperOrigin-RevId: 190306555 --- tensorflow/python/framework/meta_graph.py | 4 +++- tensorflow/python/framework/meta_graph_test.py | 15 +++++++++++++++ tensorflow/python/framework/ops.py | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/framework/meta_graph.py b/tensorflow/python/framework/meta_graph.py index 4bb9941bb7..391b17720c 100644 --- a/tensorflow/python/framework/meta_graph.py +++ b/tensorflow/python/framework/meta_graph.py @@ -737,7 +737,9 @@ def import_scoped_meta_graph(meta_graph_or_file, import_scope or "", mark_as_used=False) importer.import_graph_def( - input_graph_def, name=(import_scope or ""), input_map=input_map, + input_graph_def, + name=(import_scope or scope_to_prepend_to_names), + input_map=input_map, producer_op_list=producer_op_list) # Restores all the other collections. diff --git a/tensorflow/python/framework/meta_graph_test.py b/tensorflow/python/framework/meta_graph_test.py index 21963d0bee..5d5fb037fc 100644 --- a/tensorflow/python/framework/meta_graph_test.py +++ b/tensorflow/python/framework/meta_graph_test.py @@ -537,6 +537,21 @@ class ScopedMetaGraphTest(test.TestCase): self.assertEqual(list(imported_variables.values())[0].name, "foo/bar/myvar:0") + def testScopedImportUnderNameScopeNoVarScope(self): + graph = ops.Graph() + with graph.as_default(): + variables.Variable(initial_value=1.0, trainable=True, name="myvar") + meta_graph_def, _ = meta_graph.export_scoped_meta_graph(graph=graph) + + graph = ops.Graph() + with graph.as_default(): + with ops.name_scope("foo"): + imported_variables = meta_graph.import_scoped_meta_graph( + meta_graph_def) + self.assertEqual(len(imported_variables), 1) + self.assertEqual(list(imported_variables.values())[0].name, + "foo/myvar:0") + def testImportsUsingSameScopeName(self): with ops.Graph().as_default(): variables.Variable(0, name="v") diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 1fa9285e43..f264e38102 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -5872,6 +5872,9 @@ def strip_name_scope(name, export_scope): is None. """ if export_scope: + if export_scope[-1] == "/": + export_scope = export_scope[:-1] + try: # Strips export_scope/, export_scope///, # ^export_scope/, loc:@export_scope/. @@ -5897,6 +5900,9 @@ def prepend_name_scope(name, import_scope): is None. """ if import_scope: + if import_scope[-1] == "/": + import_scope = import_scope[:-1] + try: str_to_replace = r"([\^]|loc:@|^)(.*)" return re.sub(str_to_replace, r"\1" + import_scope + r"/\2", -- GitLab From 753f99afcd6b814781e19ae44afc6195ff68685d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 19:02:37 -0700 Subject: [PATCH 189/906] Adding support for iterating through a tf.data.Dataset for a single epoch. PiperOrigin-RevId: 190307545 --- .../contrib/py2tf/converters/for_loops.py | 30 ++++---- tensorflow/contrib/py2tf/utils/BUILD | 1 + tensorflow/contrib/py2tf/utils/__init__.py | 2 + tensorflow/contrib/py2tf/utils/builtins.py | 69 ++++++++++++++++++- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/tensorflow/contrib/py2tf/converters/for_loops.py b/tensorflow/contrib/py2tf/converters/for_loops.py index 4297c1cf2a..8d28b149a8 100644 --- a/tensorflow/contrib/py2tf/converters/for_loops.py +++ b/tensorflow/contrib/py2tf/converters/for_loops.py @@ -38,19 +38,19 @@ class ForLoopCanonicalizationTransformer(transformer.Base): self.generic_visit(node) body_scope = anno.getanno(node, NodeAnno.BODY_SCOPE) i_var = self.context.namer.new_symbol('i', body_scope.referenced) - n_var = self.context.namer.new_symbol('n', body_scope.referenced) - iterated_var = self.context.namer.new_symbol('iterated', - body_scope.referenced) + smart_loop_iter_var = self.context.namer.new_symbol('smart_loop_iter', + body_scope.referenced) + cont_var = self.context.namer.new_symbol('cont', body_scope.referenced) # TODO(mdan): Use TensorListFromTensor(loop_iter) here. if anno.hasanno(node, 'extra_cond'): template = """ i = 0 - iterated = loop_iter - n = len(iterated) - while i < n and extra_cond: - target = iterated[i] + smart_loop_iter = py2tf_utils.dynamic_dataset(loop_iter) + cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + while cont and extra_cond: body i += 1 + cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) """ return templates.replace( template, @@ -58,18 +58,18 @@ class ForLoopCanonicalizationTransformer(transformer.Base): target=node.target, body=node.body, i=i_var, - n=n_var, - iterated=iterated_var, + smart_loop_iter=smart_loop_iter_var, + cont=cont_var, extra_cond=anno.getanno(node, 'extra_cond')) else: template = """ i = 0 - iterated = loop_iter - n = len(iterated) - while i < n: - target = iterated[i] + smart_loop_iter = py2tf_utils.dynamic_dataset(loop_iter) + cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + while cont: body i += 1 + cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) """ repl = templates.replace( template, @@ -77,8 +77,8 @@ class ForLoopCanonicalizationTransformer(transformer.Base): target=node.target, body=node.body, i=i_var, - n=n_var, - iterated=iterated_var) + smart_loop_iter=smart_loop_iter_var, + cont=cont_var) return repl def visit_Continue(self, node): diff --git a/tensorflow/contrib/py2tf/utils/BUILD b/tensorflow/contrib/py2tf/utils/BUILD index d029289f5a..b53fbb5c18 100644 --- a/tensorflow/contrib/py2tf/utils/BUILD +++ b/tensorflow/contrib/py2tf/utils/BUILD @@ -35,6 +35,7 @@ py_library( deps = [ "//tensorflow/python:list_ops", "//tensorflow/python:script_ops", + "//tensorflow/python/data/ops:dataset_ops", "@six_archive//:six", ], ) diff --git a/tensorflow/contrib/py2tf/utils/__init__.py b/tensorflow/contrib/py2tf/utils/__init__.py index d9d8e34689..4e6003c852 100644 --- a/tensorflow/contrib/py2tf/utils/__init__.py +++ b/tensorflow/contrib/py2tf/utils/__init__.py @@ -19,6 +19,8 @@ from __future__ import division from __future__ import print_function from tensorflow.contrib.py2tf.utils.builtins import dynamic_builtin +from tensorflow.contrib.py2tf.utils.builtins import dynamic_dataset +from tensorflow.contrib.py2tf.utils.builtins import dynamic_for_cond from tensorflow.contrib.py2tf.utils.builtins import dynamic_print from tensorflow.contrib.py2tf.utils.builtins import dynamic_range from tensorflow.contrib.py2tf.utils.context_managers import control_dependency_on_returns diff --git a/tensorflow/contrib/py2tf/utils/builtins.py b/tensorflow/contrib/py2tf/utils/builtins.py index 3cb62b55d4..251b4ed8ee 100644 --- a/tensorflow/contrib/py2tf/utils/builtins.py +++ b/tensorflow/contrib/py2tf/utils/builtins.py @@ -22,8 +22,10 @@ import six from tensorflow.contrib.py2tf.utils import py_func from tensorflow.contrib.py2tf.utils import type_check +from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import logging_ops from tensorflow.python.ops import math_ops from tensorflow.python.util import tf_inspect @@ -54,7 +56,6 @@ def dynamic_len(list_or_tensor): raise ValueError( 'len requires non-zero rank for tensor "%s"' % list_or_tensor) return array_ops.shape(list_or_tensor)[0] - return len(list_or_tensor) @@ -97,3 +98,69 @@ def dynamic_print(*values): if all(map(is_tf_print_compatible, values)): return logging_ops.Print(1, values) return py_func.wrap_py_func(print, None, values, use_dummy_return=True) + + +def dynamic_dataset(iterated): + """Implementartion of smart tf.data.Dataset epoch wrapping. + + The function checks if the input is a tf.data.Dataset and if so then wraps it + so that for each element it returns it also returns the current epoch the + dataset iteration is in, for two epochs. If the input is not a + tf.data.Dataset then it just returns the input. + + Args: + iterated: The iterable or tf.data.Dataset that is being iterated over. + Returns: + Either just the untouched input, or in the case of input being a + tf.data.Dataset then it returns a wrapped tf.data.Dataset where for each + element it returns it also returns the current epoch the dataset iteration + is in. + """ + if not isinstance(iterated, dataset_ops.Dataset): + return iterated + + def epoch_dataset_number_helper(i): + return dataset_ops.Dataset.zip( + (dataset_ops.Dataset.from_tensors(i).repeat(), iterated)) + + epoch_numbers = dataset_ops.Dataset.range(2) + return epoch_numbers.flat_map(epoch_dataset_number_helper) + + +def dynamic_for_cond(iteration, iterated): + """Implementartion of smart while-loop condition using dynamic dispatch. + + The function checks if it is iterating over a tf.data.Dataset or not, and in + the case it is not then it simply returns if we are still in range of the + iterated and the next element. If it is iterating over a dataset then it only + iterates for a single epoch. + + Args: + iteration: The current iteration of the loop. + iterated: The iterable or tf.data.Dataset that is being iterated over. + Returns: + A tuple of a bool that indicates whether the loop should continue, and the + next element in iterated. + """ + # TODO(znado): Clean up. + # TODO(znado): This won't work for unpacked iterates. Fix. + if isinstance(iterated, dataset_ops.Dataset): + curr_epoch, next_elem = iterated.make_one_shot_iterator().get_next() + return math_ops.less(curr_epoch, 1), next_elem + elif tensor_util.is_tensor(iterated): + if iterated.shape.ndims > 1: + elem_shape = array_ops.shape(iterated)[1:] + else: + elem_shape = () + if iterated.shape.ndims == 0 or iterated.shape[0] == 0: + return False, array_ops.zeros(elem_shape, iterated.dtype) + return control_flow_ops.cond( + math_ops.less(iteration, dynamic_len(iterated)), + lambda: (True, iterated[iteration]), + lambda: (False, array_ops.zeros(elem_shape, iterated.dtype))) + elif hasattr(iterated, '__len__'): + if iteration < len(iterated): + return True, iterated[iteration] + return False, None + else: + raise NotImplementedError('Python iterators not yet supported.') -- GitLab From 759e9f874eb0af7902a586e0efcaf53463816c23 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 19:24:04 -0700 Subject: [PATCH 190/906] Fix loop variable type and status propagation PiperOrigin-RevId: 190308776 --- tensorflow/c/eager/c_api.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index c69635d529..eaeb2fd07a 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -837,7 +837,7 @@ const tensorflow::FunctionDef* OpToFunction( } VLOG(1) << "Fixed Output names and all types: " << fdef.DebugString(); - ctx->context.AddFunctionDef(fdef); + status->status = ctx->context.AddFunctionDef(fdef); if (!status->status.ok()) return nullptr; const auto ret = ctx->context.FindFunctionDef(signature->name()); DCHECK(ret != nullptr); @@ -885,7 +885,7 @@ std::unique_ptr BuildXlaLaunch(TFE_Op* op, TF_Status* status) { // Since input param reordering may have occurred between `op` and `launch_op` // via `op_input_to_func_input`, adjust the actual inputs accordingly. launch_op->inputs = op->inputs; - for (TFE_TensorHandle* h : launch_op->inputs) { + for (tensorflow::TensorHandle* h : launch_op->inputs) { h->Ref(); } if (!op_input_to_func_input.empty()) { -- GitLab From 418ae5ed77f1353c794f93a4adfbf7db02fa3191 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 20:18:46 -0700 Subject: [PATCH 191/906] A couple of small device-related utilities. PiperOrigin-RevId: 190312148 --- tensorflow/python/BUILD | 3 + tensorflow/python/training/device_util.py | 68 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tensorflow/python/training/device_util.py diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index acfdcd15f7..e6ad564ede 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -2884,9 +2884,11 @@ py_library( ":client", ":control_flow_ops", ":data_flow_ops", + ":device", ":errors", ":framework", ":framework_for_generated_wrappers", + ":framework_ops", ":gradients", ":init_ops", ":io_ops", @@ -2911,6 +2913,7 @@ py_library( ":variable_scope", ":variables", "//tensorflow/python/eager:backprop", + "//tensorflow/python/eager:context", "//third_party/py/numpy", "@six_archive//:six", ], diff --git a/tensorflow/python/training/device_util.py b/tensorflow/python/training/device_util.py new file mode 100644 index 0000000000..f1137e80ab --- /dev/null +++ b/tensorflow/python/training/device_util.py @@ -0,0 +1,68 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Device-related support functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import context +from tensorflow.python.framework import device as tf_device +from tensorflow.python.framework import ops + + +def canonicalize(d): + d = tf_device.DeviceSpec.from_string(d) + assert d.device_type is None or d.device_type == d.device_type.upper(), ( + "Device type '%s' must be all-caps." % (d.device_type,)) + # Fill in missing device fields using defaults. + result = tf_device.DeviceSpec( + job="localhost", replica=0, task=0, device_type="CPU", device_index=0) + result.merge_from(d) + return result.to_string() + + +class _FakeNodeDef(object): + """A fake NodeDef for _FakeOperation.""" + + def __init__(self): + self.op = "" + self.name = "" + + +class _FakeOperation(object): + """A fake Operation object to pass to device functions.""" + + def __init__(self): + self.device = "" + self.type = "" + self.name = "" + self.node_def = _FakeNodeDef() + + def _set_device(self, device): + self.device = ops._device_string(device) # pylint: disable=protected-access + + +def current(): + """Return a string (not canonicalized) for the current device.""" + # TODO(josh11b): Work out how this function interacts with ops.colocate_with. + ctx = context.context() + if ctx.executing_eagerly(): + d = ctx.device_name + else: + op = _FakeOperation() + ops.get_default_graph()._apply_device_functions(op) # pylint: disable=protected-access + d = op.device + return d -- GitLab From 0a335dc4fd8cae06d331589eab5858fd0a3ffc73 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 20:20:49 -0700 Subject: [PATCH 192/906] [XLA] Prevent using XlaOp from the wrong XlaBuilder. PiperOrigin-RevId: 190312254 --- .../xla/client/xla_client/xla_builder.cc | 19 +++++++++++++------ .../xla/client/xla_client/xla_builder.h | 5 +++-- .../xla/client/xla_client/xla_builder_test.cc | 11 +++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index 90f2b2d73a..cbcb747f1c 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -284,10 +284,12 @@ XlaOp XlaBuilder::Mul(const XlaOp& lhs, const XlaOp& rhs, } XlaOp XlaBuilder::ConstantLiteral(const Literal& literal) { - HloInstructionProto instr; - *instr.mutable_shape() = literal.shape(); - *instr.mutable_literal() = literal.ToProto(); - return AddInstruction(std::move(instr), HloOpcode::kConstant); + return NoteErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + *instr.mutable_shape() = literal.shape(); + *instr.mutable_literal() = literal.ToProto(); + return AddInstruction(std::move(instr), HloOpcode::kConstant); + }()); } XlaOp XlaBuilder::Call(const XlaComputation& computation, @@ -794,8 +796,9 @@ XlaOp XlaBuilder::Recv(const Shape& shape, const ChannelHandle& handle) { return UnimplementedOp(); } -XlaOp XlaBuilder::AddInstruction(HloInstructionProto&& instr, HloOpcode opcode, - tensorflow::gtl::ArraySlice operands) { +StatusOr XlaBuilder::AddInstruction( + HloInstructionProto&& instr, HloOpcode opcode, + tensorflow::gtl::ArraySlice operands) { const int64 handle = instructions_.size(); instr.set_id(handle); instr.set_opcode(HloOpcodeString(opcode)); @@ -806,6 +809,10 @@ XlaOp XlaBuilder::AddInstruction(HloInstructionProto&& instr, HloOpcode opcode, instr.set_name(StrCat(instr.name(), ".", handle)); } for (const auto& operand : operands) { + TF_RET_CHECK(operand.builder_ != nullptr); + TF_RET_CHECK(operand.builder_ == this) + << "Do not add XlaOp from builder " << operand.builder_->name() + << " to builder " << this->name(); instr.add_operand_ids(operand.handle()); // TODO(b/74197823): Set metadata and sharding. } diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index 407b2df274..99d1db7790 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -706,8 +706,9 @@ class XlaBuilder { StatusOr GetProgramShape(); private: - XlaOp AddInstruction(HloInstructionProto&& instr, HloOpcode opcode, - tensorflow::gtl::ArraySlice operands = {}); + StatusOr AddInstruction( + HloInstructionProto&& instr, HloOpcode opcode, + tensorflow::gtl::ArraySlice operands = {}); // Notes that the error occurred by: // * storing it internally and capturing a backtrace if it's the first error diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc index 10d8fa1622..57dcfc4d4d 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc @@ -179,5 +179,16 @@ TEST_F(XlaBuilderTest, BinopHasInDimAndDegenerateBroadcast) { op::Broadcast(op::Reshape(op::Parameter(1))))); } +TEST_F(XlaBuilderTest, OperandFromWrongBuilder) { + XlaBuilder b1("b1"); + auto p0 = b1.Parameter(0, ShapeUtil::MakeShape(F32, {}), "p0"); + XlaBuilder builder("main"); + builder.Add(p0, p0); + auto statusor = builder.Build(); + ASSERT_FALSE(statusor.ok()); + EXPECT_THAT(statusor.status().error_message(), + HasSubstr("Do not add XlaOp from builder b1 to builder main")); +} + } // namespace } // namespace xla -- GitLab From 917b79250b0e65aa7856b2418b68292d919cd5dc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Fri, 23 Mar 2018 22:27:31 -0700 Subject: [PATCH 193/906] [XLA] Redesign: implement Reshape and Transpose. Also, - Templatize ClientLibraryTestBase::CreateParameterAndTransferLiteral. The implementation is moved from .cc to .h because otherewise the linker complains. - Migrate some reshape tests to use the XlaBuilder. PiperOrigin-RevId: 190317960 --- .../xla/client/xla_client/xla_builder.cc | 30 ++++++++++++-- .../xla/client/xla_client/xla_builder.h | 3 +- .../xla/client/xla_client/xla_builder_test.cc | 27 +++++++++++++ tensorflow/compiler/xla/tests/BUILD | 1 + .../xla/tests/client_library_test_base.cc | 27 ------------- .../xla/tests/client_library_test_base.h | 39 +++++++++++++++++-- tensorflow/compiler/xla/tests/reshape_test.cc | 35 +++++++++-------- 7 files changed, 111 insertions(+), 51 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index cbcb747f1c..596f39b4fd 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -401,12 +401,26 @@ XlaOp XlaBuilder::Pad(const XlaOp& operand, const XlaOp& padding_value, XlaOp XlaBuilder::Reshape(const XlaOp& operand, tensorflow::gtl::ArraySlice dimensions, tensorflow::gtl::ArraySlice new_sizes) { - return UnimplementedOp(); + return NoteErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, operand.GetShape()); + TF_ASSIGN_OR_RETURN(const Shape& shape, + ShapeInference::InferReshapeShape( + operand_shape, dimensions, new_sizes)); + XlaOp transposed = IsIdentityPermutation(dimensions) + ? operand + : Transpose(operand, dimensions); + return Reshape(shape, transposed); + }()); } XlaOp XlaBuilder::Reshape(const XlaOp& operand, tensorflow::gtl::ArraySlice new_sizes) { - return UnimplementedOp(); + return NoteErrorOrReturn([&]() -> StatusOr { + TF_ASSIGN_OR_RETURN(auto shape, operand.GetShape()); + std::vector dimensions(shape.dimensions_size()); + std::iota(dimensions.begin(), dimensions.end(), 0); + return Reshape(operand, dimensions, new_sizes); + }()); } XlaOp XlaBuilder::Collapse(const XlaOp& operand, @@ -636,7 +650,17 @@ XlaOp XlaBuilder::IsFinite(const XlaOp& operand) { return UnimplementedOp(); } XlaOp XlaBuilder::Transpose(const XlaOp& operand, tensorflow::gtl::ArraySlice permutation) { - return UnimplementedOp(); + return NoteErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, operand.GetShape()); + TF_ASSIGN_OR_RETURN( + *instr.mutable_shape(), + ShapeInference::InferTransposeShape(operand_shape, permutation)); + for (int64 dim : permutation) { + instr.add_dimensions(dim); + } + return AddInstruction(std::move(instr), HloOpcode::kTranspose, {operand}); + }()); } XlaOp XlaBuilder::Rev(const XlaOp& operand, diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index 99d1db7790..c19eb47165 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -52,10 +52,11 @@ class XlaBuilder; // TODO(b/74197823): Replace xla::ComputationDataHandle with this one. class XlaOp { public: + XlaOp() : handle_(0), builder_(nullptr) {} + StatusOr GetShape() const; private: - XlaOp() : handle_(0), builder_(nullptr) {} XlaOp(int64 handle, XlaBuilder* builder) : handle_(handle), builder_(builder) {} diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc index 57dcfc4d4d..529287a57a 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc @@ -190,5 +190,32 @@ TEST_F(XlaBuilderTest, OperandFromWrongBuilder) { HasSubstr("Do not add XlaOp from builder b1 to builder main")); } +TEST_F(XlaBuilderTest, ReshapeDefaultOrder) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {2, 3, 5, 7}), "x"); + b.Reshape(x, /*new_sizes=*/{6, 35}); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, op::Reshape(op::Parameter())); +} + +TEST_F(XlaBuilderTest, ReshapeHasTranspose) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {2, 3, 5, 7}), "x"); + b.Reshape(x, /*dimensions=*/{3, 2, 1, 0}, /*new_sizes=*/{6, 35}); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, op::Reshape(op::Transpose(op::Parameter()))); +} + +TEST_F(XlaBuilderTest, Transpose) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(F32, {5, 7}), "x"); + b.Transpose(x, /*permutation=*/{1, 0}); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + auto root = module->entry_computation()->root_instruction(); + EXPECT_THAT(root, op::Transpose(op::Parameter())); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index e81e862c49..26022278e5 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -1374,6 +1374,7 @@ xla_test( "//tensorflow/compiler/xla/client:computation_builder", "//tensorflow/compiler/xla/client:global_data", "//tensorflow/compiler/xla/client:local_client", + "//tensorflow/compiler/xla/client/xla_client:xla_builder", "//tensorflow/compiler/xla/tests:client_library_test_base", "//tensorflow/compiler/xla/tests:literal_test_util", "//tensorflow/compiler/xla/tests:xla_internal_test_main", diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.cc b/tensorflow/compiler/xla/tests/client_library_test_base.cc index 3cae51576f..d9bd1ce6eb 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.cc +++ b/tensorflow/compiler/xla/tests/client_library_test_base.cc @@ -568,33 +568,6 @@ ClientLibraryTestBase::CreatePatternedMatrixWithZeroPadding(int rows, int cols, return array; } -std::unique_ptr -ClientLibraryTestBase::CreateParameterAndTransferLiteral( - int64 parameter_number, const Literal& literal, const string& name, - ComputationBuilder* builder, ComputationDataHandle* data_handle) { - return CreateParameterAndTransferLiteral(parameter_number, literal, name, - nullptr, builder, data_handle); -} - -std::unique_ptr -ClientLibraryTestBase::CreateParameterAndTransferLiteral( - int64 parameter_number, const Literal& literal, const string& name, - const DeviceHandle* device_handle, ComputationBuilder* builder, - ComputationDataHandle* data_handle) { - const Literal* param_literal = &literal; - std::unique_ptr converted_literal; - if (use_bfloat16_) { - converted_literal = LiteralTestUtil::ConvertF32ToBF16(literal); - param_literal = converted_literal.get(); - } - std::unique_ptr data = - client_->TransferToServer(*param_literal, device_handle) - .ConsumeValueOrDie(); - *data_handle = - builder->Parameter(parameter_number, param_literal->shape(), name); - return data; -} - ComputationDataHandle ClientLibraryTestBase::AddParam( const Literal& argument, ComputationBuilder* builder) { ComputationDataHandle data_handle; diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.h b/tensorflow/compiler/xla/tests/client_library_test_base.h index b553beb01a..01aa6c756f 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.h +++ b/tensorflow/compiler/xla/tests/client_library_test_base.h @@ -278,17 +278,19 @@ class ClientLibraryTestBase : public ::testing::Test { // server, then stores into "data_handle" the global handle for that // parameter. When the use_bfloat16 flag is set but the literal has F32 // elements, the literal will be converted to BF16 before being transferred. + template std::unique_ptr CreateParameterAndTransferLiteral( int64 parameter_number, const Literal& literal, const string& name, - ComputationBuilder* builder, ComputationDataHandle* data_handle); + BuilderT* builder, HandleT* data_handle); // As above, but the caller can specify the device that the literal is // transferred to. If device_handle is nullptr, the literal will be // transferred to the default device. + template std::unique_ptr CreateParameterAndTransferLiteral( int64 parameter_number, const Literal& literal, const string& name, - const DeviceHandle* device_handle, ComputationBuilder* builder, - ComputationDataHandle* data_handle); + const DeviceHandle* device_handle, BuilderT* builder, + HandleT* data_handle); // Creates a parameter instruction and sets the value that will be passed to // the computation as specified. This function must be used for all parameters @@ -652,6 +654,37 @@ std::unique_ptr> ClientLibraryTestBase::CreatePseudorandomR2( return result; } +template +std::unique_ptr +ClientLibraryTestBase::CreateParameterAndTransferLiteral(int64 parameter_number, + const Literal& literal, + const string& name, + BuilderT* builder, + HandleT* data_handle) { + return CreateParameterAndTransferLiteral(parameter_number, literal, name, + nullptr, builder, data_handle); +} + +template +std::unique_ptr +ClientLibraryTestBase::CreateParameterAndTransferLiteral( + int64 parameter_number, const Literal& literal, const string& name, + const DeviceHandle* device_handle, BuilderT* builder, + HandleT* data_handle) { + const Literal* param_literal = &literal; + std::unique_ptr converted_literal; + if (use_bfloat16_) { + converted_literal = LiteralTestUtil::ConvertF32ToBF16(literal); + param_literal = converted_literal.get(); + } + std::unique_ptr data = + client_->TransferToServer(*param_literal, device_handle) + .ConsumeValueOrDie(); + *data_handle = + builder->Parameter(parameter_number, param_literal->shape(), name); + return data; +} + } // namespace xla #endif // TENSORFLOW_COMPILER_XLA_TESTS_CLIENT_LIBRARY_TEST_BASE_H_ diff --git a/tensorflow/compiler/xla/tests/reshape_test.cc b/tensorflow/compiler/xla/tests/reshape_test.cc index f7b04debd4..02272d6017 100644 --- a/tensorflow/compiler/xla/tests/reshape_test.cc +++ b/tensorflow/compiler/xla/tests/reshape_test.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/client/global_data.h" #include "tensorflow/compiler/xla/client/local_client.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" #include "tensorflow/compiler/xla/layout_util.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/reference_util.h" @@ -207,9 +208,9 @@ XLA_TEST_P(ReshapeTest, Trivial3x1) { // // Splits an empty vector into an empty matrix. XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(R1ToR2_0_To_2x0)) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateR1({}); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{0}, @@ -221,10 +222,10 @@ XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(R1ToR2_0_To_2x0)) { // Splits a vector into a matrix. XLA_TEST_P(ReshapeTest, R1ToR2_6_To_2x3) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateR1({1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{0}, @@ -241,9 +242,9 @@ XLA_TEST_P(ReshapeTest, R1ToR2_6_To_2x3) { // // Transposes a 2x0 array to a 0x2 array. XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(Reshape0x2To2x0)) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateFromArray(Array2D(0, 2)); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{0, 1}, @@ -255,10 +256,10 @@ XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(Reshape0x2To2x0)) { // Transposes a 2-dimensional row vector to a column vector. XLA_TEST_P(ReshapeTest, ReshapeRowToCol) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto simple = MakeLinspaceArray2D(1.0f, 3.0f, 1, 3); auto input_literal = Literal::CreateFromArray(*simple); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{0, 1}, @@ -272,10 +273,10 @@ XLA_TEST_P(ReshapeTest, ReshapeRowToCol) { // Transposes a 2-dimensional array. XLA_TEST_P(ReshapeTest, TransposeAsReshape) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a4x3 = MakeLinspaceArray2D(1.0f, 12.0f, 4, 3); auto input_literal = Literal::CreateFromArray(*a4x3); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{1, 0}, @@ -291,11 +292,11 @@ XLA_TEST_P(ReshapeTest, TransposeAsReshape) { // does not handle zero-sized shapes correctly. Failed last on 2017-11-30 // with an incorrect result rank. // -// Transposes a 0x4 array with ComputationBuilder::Trans. +// Transposes a 0x4 array with XlaBuilder::Transpose. XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(Transpose0x4)) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateFromArray(Array2D(0, 4)); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Transpose(parameter, {1, 0}); @@ -306,10 +307,10 @@ XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(Transpose0x4)) { // Transposes a 2-dimensional array with ComputationBuilder::Trans. XLA_TEST_P(ReshapeTest, Transpose4x3) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a4x3 = MakeLinspaceArray2D(1.0f, 12.0f, 4, 3); auto input_literal = Literal::CreateFromArray(*a4x3); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Transpose(parameter, {1, 0}); @@ -327,9 +328,9 @@ XLA_TEST_P(ReshapeTest, Transpose4x3) { // Reshapes an empty 2-dimensional array with dimensions that are not just a // rearrangement of the originals (split), but no reordering (no shuffle). XLA_TEST_P(ReshapeTest, DISABLED_ON_GPU(ReshapeSplitNoShuffleZeroElements)) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateFromArray(Array2D(6, 0)); - ComputationDataHandle parameter; + XlaOp parameter; auto input = CreateParameterAndTransferLiteral(0, *input_literal, "input", &builder, ¶meter); builder.Reshape(/*operand=*/parameter, /*dimensions=*/{0, 1}, -- GitLab From f95347a96c431b63183856128bfea3943585f938 Mon Sep 17 00:00:00 2001 From: Francois Chollet Date: Fri, 23 Mar 2018 23:04:03 -0700 Subject: [PATCH 194/906] Trivial update of layer imports in eager execution examples, to reflect recommended practices. PiperOrigin-RevId: 190319480 --- .../eager/python/examples/gan/mnist.py | 21 ++++----- .../linear_regression/linear_regression.py | 4 +- .../python/examples/resnet50/resnet50.py | 43 ++++++++++--------- .../examples/rnn_colorbot/rnn_colorbot.py | 6 ++- .../eager/python/examples/rnn_ptb/rnn_ptb.py | 8 ++-- 5 files changed, 46 insertions(+), 36 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/gan/mnist.py b/tensorflow/contrib/eager/python/examples/gan/mnist.py index 2b7e199fad..b80c909023 100644 --- a/tensorflow/contrib/eager/python/examples/gan/mnist.py +++ b/tensorflow/contrib/eager/python/examples/gan/mnist.py @@ -32,6 +32,7 @@ import tensorflow as tf import tensorflow.contrib.eager as tfe from tensorflow.examples.tutorials.mnist import input_data +layers = tf.keras.layers FLAGS = None @@ -56,15 +57,15 @@ class Discriminator(tf.keras.Model): else: assert data_format == 'channels_last' self._input_shape = [-1, 28, 28, 1] - self.conv1 = tf.layers.Conv2D( + self.conv1 = layers.Conv2D( 64, 5, padding='SAME', data_format=data_format, activation=tf.tanh) - self.pool1 = tf.layers.AveragePooling2D(2, 2, data_format=data_format) - self.conv2 = tf.layers.Conv2D( + self.pool1 = layers.AveragePooling2D(2, 2, data_format=data_format) + self.conv2 = layers.Conv2D( 128, 5, data_format=data_format, activation=tf.tanh) - self.pool2 = tf.layers.AveragePooling2D(2, 2, data_format=data_format) - self.flatten = tf.layers.Flatten() - self.fc1 = tf.layers.Dense(1024, activation=tf.tanh) - self.fc2 = tf.layers.Dense(1, activation=None) + self.pool2 = layers.AveragePooling2D(2, 2, data_format=data_format) + self.flatten = layers.Flatten() + self.fc1 = layers.Dense(1024, activation=tf.tanh) + self.fc2 = layers.Dense(1, activation=None) def call(self, inputs): """Return two logits per image estimating input authenticity. @@ -112,16 +113,16 @@ class Generator(tf.keras.Model): else: assert data_format == 'channels_last' self._pre_conv_shape = [-1, 6, 6, 128] - self.fc1 = tf.layers.Dense(6 * 6 * 128, activation=tf.tanh) + self.fc1 = layers.Dense(6 * 6 * 128, activation=tf.tanh) # In call(), we reshape the output of fc1 to _pre_conv_shape # Deconvolution layer. Resulting image shape: (batch, 14, 14, 64) - self.conv1 = tf.layers.Conv2DTranspose( + self.conv1 = layers.Conv2DTranspose( 64, 4, strides=2, activation=None, data_format=data_format) # Deconvolution layer. Resulting image shape: (batch, 28, 28, 1) - self.conv2 = tf.layers.Conv2DTranspose( + self.conv2 = layers.Conv2DTranspose( 1, 2, strides=2, activation=tf.nn.sigmoid, data_format=data_format) def call(self, inputs): diff --git a/tensorflow/contrib/eager/python/examples/linear_regression/linear_regression.py b/tensorflow/contrib/eager/python/examples/linear_regression/linear_regression.py index 6ab847cb78..4e1380afb2 100644 --- a/tensorflow/contrib/eager/python/examples/linear_regression/linear_regression.py +++ b/tensorflow/contrib/eager/python/examples/linear_regression/linear_regression.py @@ -32,6 +32,8 @@ import tensorflow as tf import tensorflow.contrib.eager as tfe +layers = tf.keras.layers + class LinearModel(tf.keras.Model): """A TensorFlow linear regression model.""" @@ -39,7 +41,7 @@ class LinearModel(tf.keras.Model): def __init__(self): """Constructs a LinearModel object.""" super(LinearModel, self).__init__() - self._hidden_layer = tf.layers.Dense(1) + self._hidden_layer = layers.Dense(1) def call(self, xs): """Invoke the linear model. diff --git a/tensorflow/contrib/eager/python/examples/resnet50/resnet50.py b/tensorflow/contrib/eager/python/examples/resnet50/resnet50.py index 6b59413141..a28bc8a43d 100644 --- a/tensorflow/contrib/eager/python/examples/resnet50/resnet50.py +++ b/tensorflow/contrib/eager/python/examples/resnet50/resnet50.py @@ -28,6 +28,8 @@ import functools import tensorflow as tf +layers = tf.keras.layers + class _IdentityBlock(tf.keras.Model): """_IdentityBlock is the block that has no conv layer at shortcut. @@ -49,23 +51,23 @@ class _IdentityBlock(tf.keras.Model): bn_name_base = 'bn' + str(stage) + block + '_branch' bn_axis = 1 if data_format == 'channels_first' else 3 - self.conv2a = tf.layers.Conv2D( + self.conv2a = layers.Conv2D( filters1, (1, 1), name=conv_name_base + '2a', data_format=data_format) - self.bn2a = tf.layers.BatchNormalization( + self.bn2a = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2a') - self.conv2b = tf.layers.Conv2D( + self.conv2b = layers.Conv2D( filters2, kernel_size, padding='same', data_format=data_format, name=conv_name_base + '2b') - self.bn2b = tf.layers.BatchNormalization( + self.bn2b = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2b') - self.conv2c = tf.layers.Conv2D( + self.conv2c = layers.Conv2D( filters3, (1, 1), name=conv_name_base + '2c', data_format=data_format) - self.bn2c = tf.layers.BatchNormalization( + self.bn2c = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2c') def call(self, input_tensor, training=False): @@ -113,34 +115,34 @@ class _ConvBlock(tf.keras.Model): bn_name_base = 'bn' + str(stage) + block + '_branch' bn_axis = 1 if data_format == 'channels_first' else 3 - self.conv2a = tf.layers.Conv2D( + self.conv2a = layers.Conv2D( filters1, (1, 1), strides=strides, name=conv_name_base + '2a', data_format=data_format) - self.bn2a = tf.layers.BatchNormalization( + self.bn2a = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2a') - self.conv2b = tf.layers.Conv2D( + self.conv2b = layers.Conv2D( filters2, kernel_size, padding='same', name=conv_name_base + '2b', data_format=data_format) - self.bn2b = tf.layers.BatchNormalization( + self.bn2b = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2b') - self.conv2c = tf.layers.Conv2D( + self.conv2c = layers.Conv2D( filters3, (1, 1), name=conv_name_base + '2c', data_format=data_format) - self.bn2c = tf.layers.BatchNormalization( + self.bn2c = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '2c') - self.conv_shortcut = tf.layers.Conv2D( + self.conv_shortcut = layers.Conv2D( filters3, (1, 1), strides=strides, name=conv_name_base + '1', data_format=data_format) - self.bn_shortcut = tf.layers.BatchNormalization( + self.bn_shortcut = layers.BatchNormalization( axis=bn_axis, name=bn_name_base + '1') def call(self, input_tensor, training=False): @@ -219,15 +221,15 @@ class ResNet50(tf.keras.Model): return _IdentityBlock( 3, filters, stage=stage, block=block, data_format=data_format) - self.conv1 = tf.layers.Conv2D( + self.conv1 = layers.Conv2D( 64, (7, 7), strides=(2, 2), data_format=data_format, padding='same', name='conv1') bn_axis = 1 if data_format == 'channels_first' else 3 - self.bn_conv1 = tf.layers.BatchNormalization(axis=bn_axis, name='bn_conv1') - self.max_pool = tf.layers.MaxPooling2D( + self.bn_conv1 = layers.BatchNormalization(axis=bn_axis, name='bn_conv1') + self.max_pool = layers.MaxPooling2D( (3, 3), strides=(2, 2), data_format=data_format) self.l2a = conv_block([64, 64, 256], stage=2, block='a', strides=(1, 1)) @@ -250,11 +252,12 @@ class ResNet50(tf.keras.Model): self.l5b = id_block([512, 512, 2048], stage=5, block='b') self.l5c = id_block([512, 512, 2048], stage=5, block='c') - self.avg_pool = tf.layers.AveragePooling2D( + self.avg_pool = layers.AveragePooling2D( (7, 7), strides=(7, 7), data_format=data_format) if self.include_top: - self.fc1000 = tf.layers.Dense(classes, name='fc1000') + self.flatten = layers.Flatten() + self.fc1000 = layers.Dense(classes, name='fc1000') else: reduction_indices = [1, 2] if data_format == 'channels_last' else [2, 3] reduction_indices = tf.constant(reduction_indices) @@ -298,7 +301,7 @@ class ResNet50(tf.keras.Model): x = self.avg_pool(x) if self.include_top: - return self.fc1000(tf.layers.flatten(x)) + return self.fc1000(self.flatten(x)) elif self.global_pooling: return self.global_pooling(x) else: diff --git a/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py b/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py index 88fffc962f..492adbe1d8 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py +++ b/tensorflow/contrib/eager/python/examples/rnn_colorbot/rnn_colorbot.py @@ -73,6 +73,8 @@ try: except ImportError: HAS_MATPLOTLIB = False +layers = tf.keras.layers + def parse(line): """Parse a line from the colors dataset.""" @@ -152,7 +154,7 @@ class RNNColorbot(tf.keras.Model): self.cells = self._add_cells( [tf.nn.rnn_cell.BasicLSTMCell(size) for size in rnn_cell_sizes]) - self.relu = tf.layers.Dense( + self.relu = layers.Dense( label_dimension, activation=tf.nn.relu, name="relu") def call(self, inputs, training=False): @@ -204,7 +206,7 @@ class RNNColorbot(tf.keras.Model): def _add_cells(self, cells): # "Magic" required for keras.Model classes to track all the variables in - # a list of tf.layers.Layer objects. + # a list of layers.Layer objects. # TODO(ashankar): Figure out API so user code doesn't have to do this. for i, c in enumerate(cells): setattr(self, "cell-%d" % i, c) diff --git a/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py b/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py index 69cd16d12c..a90048d813 100644 --- a/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py +++ b/tensorflow/contrib/eager/python/examples/rnn_ptb/rnn_ptb.py @@ -38,6 +38,8 @@ import tensorflow as tf from tensorflow.contrib.cudnn_rnn.python.layers import cudnn_rnn from tensorflow.contrib.eager.python import tfe +layers = tf.keras.layers + class RNN(tf.keras.Model): """A static RNN. @@ -74,14 +76,14 @@ class RNN(tf.keras.Model): def _add_cells(self, cells): # "Magic" required for keras.Model classes to track all the variables in - # a list of tf.layers.Layer objects. + # a list of Layer objects. # TODO(ashankar): Figure out API so user code doesn't have to do this. for i, c in enumerate(cells): setattr(self, "cell-%d" % i, c) return cells -class Embedding(tf.layers.Layer): +class Embedding(layers.Layer): """An Embedding layer.""" def __init__(self, vocab_size, embedding_dim, **kwargs): @@ -132,7 +134,7 @@ class PTBModel(tf.keras.Model): else: self.rnn = RNN(hidden_dim, num_layers, self.keep_ratio) - self.linear = tf.layers.Dense( + self.linear = layers.Dense( vocab_size, kernel_initializer=tf.random_uniform_initializer(-0.1, 0.1)) self._output_shape = [-1, embedding_dim] -- GitLab From d8eda53c488683b37ae60e2ecbdf0bd2fd47c8c1 Mon Sep 17 00:00:00 2001 From: Dimitris Vardoulakis Date: Sat, 24 Mar 2018 00:24:50 -0700 Subject: [PATCH 195/906] Misc typo fixes in the XLA sources and docs. PiperOrigin-RevId: 190322644 --- tensorflow/compiler/xla/service/algebraic_simplifier.cc | 2 +- tensorflow/compiler/xla/service/algebraic_simplifier.h | 4 ++-- tensorflow/compiler/xla/service/compiler.h | 2 +- tensorflow/compiler/xla/service/while_loop_simplifier.h | 2 +- tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 02b23c2d14..f9fabd8a35 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -302,7 +302,7 @@ class AlgebraicSimplifierVisitor : public DfsHloVisitorWithDefault { // Disable dot strength reduction on platforms where it causes a slowdown. bool enable_dot_strength_reduction_; - // Disable convolution simplication on platforms where it causes a slowdown. + // Disable convolution simplification on platforms where it causes a slowdown. bool enable_conv_simplification_; }; diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.h b/tensorflow/compiler/xla/service/algebraic_simplifier.h index f0590943be..c48196e861 100644 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.h +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.h @@ -57,10 +57,10 @@ class AlgebraicSimplifier : public HloPassInterface { bool is_layout_sensitive_; ValidBitcastCallback valid_bitcast_callback_; - // Enable dot simplication on platforms where it is profitable. + // Enable dot simplification on platforms where it is profitable. bool enable_dot_strength_reduction_; - // Enable convolution simplication on platforms where it is profitable. + // Enable convolution simplification on platforms where it is profitable. bool enable_conv_simplification_; }; diff --git a/tensorflow/compiler/xla/service/compiler.h b/tensorflow/compiler/xla/service/compiler.h index 33e19efc72..b4b53ae2ed 100644 --- a/tensorflow/compiler/xla/service/compiler.h +++ b/tensorflow/compiler/xla/service/compiler.h @@ -127,7 +127,7 @@ class Compiler { // Compiles the HLO module for execution on a device given by the executor, // and returns an executable object or an error status. No HLO passes are // applied to module. Generally a module should be passed through RunHloPasses - // prior to calling this method because the some HLO passes are required for + // prior to calling this method because some HLO passes are required for // correctness. Takes ownership of the HLO module and is free to transform it. // // The compiler may optionally specialize to the individual device diff --git a/tensorflow/compiler/xla/service/while_loop_simplifier.h b/tensorflow/compiler/xla/service/while_loop_simplifier.h index d3d55634c9..3d3e1d60f2 100644 --- a/tensorflow/compiler/xla/service/while_loop_simplifier.h +++ b/tensorflow/compiler/xla/service/while_loop_simplifier.h @@ -25,7 +25,7 @@ namespace xla { // HLO pass that makes the following transformations on while loops: // // - A while loop with static trip count of 0 is deleted. -// - A while loops with static trip count of 1 is replaced by its body (sans +// - A while loop with static trip count of 1 is replaced by its body (sans // loop). // - Elements of a while loop's tuple that the loop doesn't use are removed // from the tuple. diff --git a/tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h b/tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h index 063e312df6..8763e588c4 100644 --- a/tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h +++ b/tensorflow/compiler/xla/service/zero_sized_hlo_elimination.h @@ -19,7 +19,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/service/hlo_pass_interface.h" -// HLO pass that replaces zero sized Hlos with an zero sized constant literal. +// HLO pass that replaces zero sized Hlos with a zero sized constant literal. namespace xla { class ZeroSizedHloElimination : public HloPassInterface { public: -- GitLab From 1aa398fe9801bca4dd8e19c255634d93bc9f5456 Mon Sep 17 00:00:00 2001 From: Priya Gupta Date: Sat, 24 Mar 2018 23:42:08 -0700 Subject: [PATCH 196/906] Build and import rules for distributed strategy PiperOrigin-RevId: 190367484 --- tensorflow/tools/docs/generate_lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index 34dd419f15..d22a465376 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -211,6 +211,7 @@ def _get_default_do_not_descend_map(): 'tf': ['cli', 'lib', 'wrappers'], 'tf.contrib': [ 'compiler', + 'distribute', 'grid_rnn', # Block contrib.keras to de-clutter the docs 'keras', -- GitLab From 3e4df091fd099170ccb9737be3747b9542a85669 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 25 Mar 2018 09:38:54 -0700 Subject: [PATCH 197/906] Restore dependencies that are needed by the PIP package builder PiperOrigin-RevId: 190387090 --- .../contrib/boosted_trees/estimator_batch/BUILD | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD index dae402204f..dcd235f876 100644 --- a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD +++ b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD @@ -13,20 +13,23 @@ load("//tensorflow:tensorflow.bzl", "py_test") filegroup( name = "all_files", srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], + include = ["**/*"], + exclude = ["**/OWNERS"], ), visibility = ["//tensorflow:__subpackages__"], ) py_library( name = "init_py", - srcs = [ - "__init__.py", - ], + srcs = ["__init__.py"], srcs_version = "PY2AND3", + deps = [ + "custom_export_strategy", + ":custom_loss_head", + ":estimator", + ":model", + ":trainer_hooks", + ], ) py_library( -- GitLab From 6c1737e6c8c9e5db405853178fb5e42abc080ba3 Mon Sep 17 00:00:00 2001 From: brett koonce Date: Sun, 25 Mar 2018 11:49:51 -0700 Subject: [PATCH 198/906] contrib/factorization: minor spelling tweaks (#17992) --- .../factorization/kernels/clustering_ops.cc | 2 +- .../factorization/python/ops/factorization_ops.py | 14 +++++++------- .../python/ops/factorization_ops_test.py | 8 ++++---- .../contrib/factorization/python/ops/gmm_ops.py | 4 ++-- .../contrib/factorization/python/ops/gmm_test.py | 2 +- .../factorization/python/ops/kmeans_test.py | 4 ++-- .../contrib/factorization/python/ops/wals.py | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tensorflow/contrib/factorization/kernels/clustering_ops.cc b/tensorflow/contrib/factorization/kernels/clustering_ops.cc index dd61f59585..2a6c97e8b9 100644 --- a/tensorflow/contrib/factorization/kernels/clustering_ops.cc +++ b/tensorflow/contrib/factorization/kernels/clustering_ops.cc @@ -353,7 +353,7 @@ class NearestNeighborsOp : public OpKernel { auto worker_threads = *(context->device()->tensorflow_cpu_worker_threads()); const int64 num_threads = worker_threads.num_threads; // This kernel might be configured to use fewer than the total number of - // available CPUs on the host machine. To avoid descructive interference + // available CPUs on the host machine. To avoid destructive interference // with other jobs running on the host machine, we must only use a fraction // of total available L3 cache. Unfortunately, we cannot query the host // machine to get the number of physical CPUs. So, we use a fixed per-CPU diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops.py b/tensorflow/contrib/factorization/python/ops/factorization_ops.py index 054888e734..8e0ed1d80e 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops.py @@ -106,7 +106,7 @@ class WALSModel(object): # the prep_gramian_op for row(column) can be run. worker_init_op = model.worker_init - # To be run once per interation sweep before the row(column) update + # To be run once per integration sweep before the row(column) update # initialize ops can be run. Note that in the distributed training # situations, this should only be run by the chief trainer. All other # trainers need to block until this is done. @@ -118,9 +118,9 @@ class WALSModel(object): init_row_update_op = model.initialize_row_update_op init_col_update_op = model.initialize_col_update_op - # Ops to upate row(column). This can either take the entire sparse tensor - # or slices of sparse tensor. For distributed trainer, each trainer - # handles just part of the matrix. + # Ops to update row(column). This can either take the entire sparse + # tensor or slices of sparse tensor. For distributed trainer, each + # trainer handles just part of the matrix. _, row_update_op, unreg_row_loss, row_reg, _ = model.update_row_factors( sp_input=matrix_slices_from_queue_for_worker_shard) row_loss = unreg_row_loss + row_reg @@ -220,7 +220,7 @@ class WALSModel(object): in the form of [[w_0, w_1, ...], [w_k, ... ], [...]], with the number of inner lists matching the number of row factor shards and the elements in each inner list are the weights for the rows of the corresponding row - factor shard. In this case, w_ij = unonbserved_weight + + factor shard. In this case, w_ij = unobserved_weight + row_weights[i] * col_weights[j]. - If this is a single non-negative real number, this value is used for all row weights and w_ij = unobserved_weight + row_weights * @@ -435,7 +435,7 @@ class WALSModel(object): gramian: Variable storing the gramian calculated from the factors. Returns: - A op that updates the gramian with the calcuated value from the factors. + A op that updates the gramian with the calculated value from the factors. """ partial_gramians = [] for f in factors: @@ -564,7 +564,7 @@ class WALSModel(object): Note that specifically this initializes the cache of the row and column weights on workers when `use_factors_weights_cache` is True. In this case, - if these weights are being calcualted and reset after the object is created, + if these weights are being calculated and reset after the object is created, it is important to ensure this ops is run afterwards so the cache reflects the correct values. """ diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py index c813733915..002f9cfbdd 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py @@ -210,7 +210,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reprodue the same row factors in the model as the + # This is expected to reproduce the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -283,7 +283,7 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 3 column feature vectors. - # This is expected to reprodue the same column factors in the model as the + # This is expected to reproduce the same column factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( @@ -385,7 +385,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reprodue the same row factors in the model as the + # This is expected to reproduce the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -462,7 +462,7 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 2 column feature vectors. - # This is expected to reprodue the same column factors in the model as the + # This is expected to reproduce the same column factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( diff --git a/tensorflow/contrib/factorization/python/ops/gmm_ops.py b/tensorflow/contrib/factorization/python/ops/gmm_ops.py index 98d6434f47..14d4c733e3 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_ops.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_ops.py @@ -280,7 +280,7 @@ class GmmAlgorithm(object): self._define_score_samples() def _define_full_covariance_probs(self, shard_id, shard): - """Defines the full covariance probabilties per example in a class. + """Defines the full covariance probabilities per example in a class. Updates a matrix with dimension num_examples X num_classes. @@ -344,7 +344,7 @@ class GmmAlgorithm(object): def _define_prior_log_prob_operation(self, shard_id): """Computes the prior probability of all samples. - Updates a vector where each item is the prior probabibility of an + Updates a vector where each item is the prior probability of an input example. Args: diff --git a/tensorflow/contrib/factorization/python/ops/gmm_test.py b/tensorflow/contrib/factorization/python/ops/gmm_test.py index 00a4734eb6..4fc9c96e9d 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_test.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_test.py @@ -210,7 +210,7 @@ class GMMTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependendent on inputs, if input + # Note that since cluster initialization is dependent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/kmeans_test.py b/tensorflow/contrib/factorization/python/ops/kmeans_test.py index 0103cc4439..88eb9cf692 100644 --- a/tensorflow/contrib/factorization/python/ops/kmeans_test.py +++ b/tensorflow/contrib/factorization/python/ops/kmeans_test.py @@ -413,7 +413,7 @@ class KMeansCosineDistanceTest(KMeansTestBase): self.assertAllClose(score, self.true_score, atol=1e-2) def test_predict_kmeans_plus_plus(self): - # Most points are concetrated near one center. KMeans++ is likely to find + # Most points are concentrated near one center. KMeans++ is likely to find # the less populated centers. points = np.array( [[2.5, 3.5], [2.5, 3.5], [-2, 3], [-2, 3], [-3, -3], [-3.1, -3.2], @@ -604,7 +604,7 @@ class KMeansTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependendent on inputs, if input + # Note that since cluster initialization is dependent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/wals.py b/tensorflow/contrib/factorization/python/ops/wals.py index 4fe22ea26e..62db3bb4c4 100644 --- a/tensorflow/contrib/factorization/python/ops/wals.py +++ b/tensorflow/contrib/factorization/python/ops/wals.py @@ -235,7 +235,7 @@ def _wals_factorization_model_function(features, labels, mode, params): num_items: An integer, the total number of items of this axis. update_fn: A function that takes one argument (`sp_input`), and that returns a tuple of - * new_factors: A flot Tensor of the factor values after update. + * new_factors: A float Tensor of the factor values after update. * update_op: a TensorFlow op which updates the factors. * loss: A float Tensor, the unregularized loss. * reg_loss: A float Tensor, the regularization loss. -- GitLab From 6645609dffd4bfeb33d4d7250ad8e06935c39e82 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 25 Mar 2018 11:51:33 -0700 Subject: [PATCH 199/906] Add skeleton code for DebugStripper. PiperOrigin-RevId: 190391193 --- tensorflow/core/grappler/optimizers/BUILD | 32 ++++++++++++++ .../grappler/optimizers/debug_stripper.cc | 36 +++++++++++++++ .../core/grappler/optimizers/debug_stripper.h | 43 ++++++++++++++++++ .../optimizers/debug_stripper_test.cc | 44 +++++++++++++++++++ .../grappler/optimizers/meta_optimizer.cc | 14 +++++- .../core/grappler/utils/grappler_test.cc | 1 + .../core/protobuf/rewriter_config.proto | 2 + 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 tensorflow/core/grappler/optimizers/debug_stripper.cc create mode 100644 tensorflow/core/grappler/optimizers/debug_stripper.h create mode 100644 tensorflow/core/grappler/optimizers/debug_stripper_test.cc diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 92f7cce502..601984fcfd 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -500,6 +500,7 @@ cc_library( ":constant_folding", ":custom_graph_optimizer", ":custom_graph_optimizer_registry", + ":debug_stripper", ":dependency_optimizer", ":function_optimizer", ":graph_optimizer", @@ -618,3 +619,34 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) + +cc_library( + name = "debug_stripper", + srcs = ["debug_stripper.cc"], + hdrs = [ + "debug_stripper.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core/grappler:grappler_item", + "//tensorflow/core/grappler/clusters:cluster", + "//tensorflow/core/grappler/optimizers:graph_optimizer", + ], +) + +tf_cuda_cc_test( + name = "debug_stripper_test", + size = "small", + srcs = ["debug_stripper_test.cc"], + deps = [ + ":debug_stripper", + "//tensorflow/cc:cc_ops", + "//tensorflow/core:tensorflow", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core/grappler:grappler_item", + "//tensorflow/core/grappler/utils:grappler_test", + ], +) diff --git a/tensorflow/core/grappler/optimizers/debug_stripper.cc b/tensorflow/core/grappler/optimizers/debug_stripper.cc new file mode 100644 index 0000000000..461f1aa2fb --- /dev/null +++ b/tensorflow/core/grappler/optimizers/debug_stripper.cc @@ -0,0 +1,36 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/grappler/optimizers/debug_stripper.h" +#include "tensorflow/core/grappler/clusters/cluster.h" +#include "tensorflow/core/grappler/grappler_item.h" + +namespace tensorflow { +namespace grappler { + +Status DebugStripper::Optimize(Cluster* cluster, const GrapplerItem& item, + GraphDef* output) { + // TODO(haoliang): Let's remove assertions here. + *output = item.graph; + return Status::OK(); +} + +void DebugStripper::Feedback(Cluster* cluster, const GrapplerItem& item, + const GraphDef& optimize_output, double result) { + // Takes no feedback. +} + +} // end namespace grappler +} // end namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/debug_stripper.h b/tensorflow/core/grappler/optimizers/debug_stripper.h new file mode 100644 index 0000000000..1fe25aa1c3 --- /dev/null +++ b/tensorflow/core/grappler/optimizers/debug_stripper.h @@ -0,0 +1,43 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEBUG_STRIPPER_H_ +#define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEBUG_STRIPPER_H_ + +#include "tensorflow/core/grappler/optimizers/graph_optimizer.h" + +namespace tensorflow { +namespace grappler { + +// DebugStripper strips off debug-related nodes (e.g. +// Assert, CheckNumerics, Print) from the graph. +class DebugStripper : public GraphOptimizer { + public: + DebugStripper() {} + ~DebugStripper() override {} + + string name() const override { return "debug_stripper"; }; + + Status Optimize(Cluster* cluster, const GrapplerItem& item, + GraphDef* output) override; + + void Feedback(Cluster* cluster, const GrapplerItem& item, + const GraphDef& optimize_output, double result) override; +}; + +} // end namespace grappler +} // end namespace tensorflow + +#endif // TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DEBUG_STRIPPER_H_ diff --git a/tensorflow/core/grappler/optimizers/debug_stripper_test.cc b/tensorflow/core/grappler/optimizers/debug_stripper_test.cc new file mode 100644 index 0000000000..d2cabc0798 --- /dev/null +++ b/tensorflow/core/grappler/optimizers/debug_stripper_test.cc @@ -0,0 +1,44 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/grappler/optimizers/debug_stripper.h" + +#include "tensorflow/cc/ops/standard_ops.h" +#include "tensorflow/core/grappler/grappler_item.h" +#include "tensorflow/core/grappler/utils/grappler_test.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace grappler { +namespace { + +class DebugStripperTest : public GrapplerTest {}; + +// TODO(haoliang): Add tests for different removal operations. +TEST_F(DebugStripperTest, OutputEqualToInput) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + auto c = ops::Const(s.WithOpName("c"), 0, {}); + GrapplerItem item; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + + DebugStripper optimizer; + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); +} + +} // namespace +} // namespace grappler +} // namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc index 6eb2bbc547..47ec16226b 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/core/grappler/optimizers/auto_parallel.h" #include "tensorflow/core/grappler/optimizers/constant_folding.h" #include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h" +#include "tensorflow/core/grappler/optimizers/debug_stripper.h" #include "tensorflow/core/grappler/optimizers/dependency_optimizer.h" #include "tensorflow/core/grappler/optimizers/function_optimizer.h" #include "tensorflow/core/grappler/optimizers/graph_optimizer.h" @@ -84,6 +85,9 @@ std::unique_ptr MetaOptimizer::NewOptimizer( graph_optimizer.reset( new DependencyOptimizer(cfg_.dependency_optimization())); } + if (optimizer == "debug_stripper") { + graph_optimizer.reset(new DebugStripper()); + } return graph_optimizer; } @@ -134,10 +138,15 @@ Status MetaOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, optimizers.push_back(std::unique_ptr( new AutoParallel(cfg_.auto_parallel().num_replicas()))); } + if (cfg_.debug_stripper() == RewriterConfig::ON) { + optimizers.push_back( + std::unique_ptr(new DebugStripper())); + } } else { const std::set available_optimizers = { - "pruning", "function", "constfold", "layout", "memory", - "autoparallel", "arithmetic", "loop", "dependency"}; + "pruning", "function", "constfold", "layout", + "memory", "autoparallel", "arithmetic", "loop", + "dependency", "debug_stripper"}; std::vector custom_optimizer_names; for (const auto& optimizer_name : cfg_.optimizers()) { if (available_optimizers.find(optimizer_name) != @@ -238,6 +247,7 @@ bool MetaOptimizerEnabled(const RewriterConfig& cfg) { cfg.dependency_optimization() != RewriterConfig::OFF || cfg.auto_parallel().enable() || cfg.memory_optimization() != RewriterConfig::NO_MEM_OPT || + cfg.debug_stripper() == RewriterConfig::ON || !cfg.optimizers().empty(); } diff --git a/tensorflow/core/grappler/utils/grappler_test.cc b/tensorflow/core/grappler/utils/grappler_test.cc index 1c15ea65b8..ee126f4955 100644 --- a/tensorflow/core/grappler/utils/grappler_test.cc +++ b/tensorflow/core/grappler/utils/grappler_test.cc @@ -36,6 +36,7 @@ GrapplerTest::GrapplerTest() { cfg->set_loop_optimization(RewriterConfig::OFF); cfg->set_function_optimization(RewriterConfig::OFF); cfg->set_layout_optimizer(RewriterConfig::OFF); + cfg->set_debug_stripper(RewriterConfig::OFF); } std::vector GrapplerTest::EvaluateNodes( diff --git a/tensorflow/core/protobuf/rewriter_config.proto b/tensorflow/core/protobuf/rewriter_config.proto index fdf16aa1da..bb772460b0 100644 --- a/tensorflow/core/protobuf/rewriter_config.proto +++ b/tensorflow/core/protobuf/rewriter_config.proto @@ -46,6 +46,8 @@ message RewriterConfig { Toggle loop_optimization = 9; // Function optimizations (default is ON). Toggle function_optimization = 10; + // Strips debug-related nodes from the graph (off by default). + Toggle debug_stripper = 11; // If true, don't remove unnecessary ops from the graph bool disable_model_pruning = 2; -- GitLab From 8561c30ea6538083248b653237754138695702af Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Sun, 25 Mar 2018 19:35:54 -0700 Subject: [PATCH 200/906] Use compat.as_bytes() instead of str.encode(). PiperOrigin-RevId: 190409217 --- tensorflow/python/framework/ops.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index f264e38102..5e4a884a70 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -2477,8 +2477,9 @@ def _set_shapes_for_outputs_c_api(op): serialized = c_api.ResourceHandleShapeAndType(op._graph._c_graph, output._as_tf_output()) if serialized: - output._handle_data = (cpp_shape_inference_pb2.CppShapeInferenceResult - .HandleData.FromString(serialized.encode())) + output._handle_data = ( + cpp_shape_inference_pb2.CppShapeInferenceResult.HandleData.FromString( + compat.as_bytes(serialized))) else: output._handle_data = None -- GitLab From 9d9ea88abd63d2c317e445e54a4f9c90d747343a Mon Sep 17 00:00:00 2001 From: Petros Mol Date: Sun, 25 Mar 2018 20:13:13 -0700 Subject: [PATCH 201/906] Minor Error type and documentation fix. PiperOrigin-RevId: 190411045 --- tensorflow/python/estimator/canned/head.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/estimator/canned/head.py b/tensorflow/python/estimator/canned/head.py index c9635a9c27..bb033d3495 100644 --- a/tensorflow/python/estimator/canned/head.py +++ b/tensorflow/python/estimator/canned/head.py @@ -887,11 +887,12 @@ def _binary_logistic_head_with_sigmoid_cross_entropy_loss( Raises: ValueError: If `thresholds` contains a value outside of `(0, 1)`. ValueError: If `loss_reduction` is invalid. + TypeError: if `label_vocabulary` has invalid type. """ thresholds = tuple(thresholds) if thresholds else tuple() if label_vocabulary is not None and not isinstance(label_vocabulary, (list, tuple)): - raise ValueError( + raise TypeError( 'label_vocabulary should be a list or tuple. Given type: {}'.format( type(label_vocabulary))) -- GitLab From 668f182b1fdfc31568a44fe650324fe2ddedbbe1 Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Sun, 25 Mar 2018 21:57:09 -0700 Subject: [PATCH 202/906] Always cast `tf.distributions.Distribution` `_event_shape`, `_batch_shape`. PiperOrigin-RevId: 190415923 --- tensorflow/python/ops/distributions/distribution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/distributions/distribution.py b/tensorflow/python/ops/distributions/distribution.py index c055ca43e8..0866fa8b0b 100644 --- a/tensorflow/python/ops/distributions/distribution.py +++ b/tensorflow/python/ops/distributions/distribution.py @@ -593,7 +593,7 @@ class Distribution(_BaseDistribution): Returns: batch_shape: `TensorShape`, possibly unknown. """ - return self._batch_shape() + return tensor_shape.as_shape(self._batch_shape()) def _event_shape_tensor(self): raise NotImplementedError("event_shape_tensor is not implemented") @@ -626,7 +626,7 @@ class Distribution(_BaseDistribution): Returns: event_shape: `TensorShape`, possibly unknown. """ - return self._event_shape() + return tensor_shape.as_shape(self._event_shape()) def is_scalar_event(self, name="is_scalar_event"): """Indicates that `event_shape == []`. -- GitLab From c3436d6757a77ab1fefd3f6000a1e961a9ab9881 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Sun, 25 Mar 2018 22:02:09 -0700 Subject: [PATCH 203/906] Disable flaky prefetching_ops_test. PiperOrigin-RevId: 190416108 --- tensorflow/contrib/data/python/kernel_tests/BUILD | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tensorflow/contrib/data/python/kernel_tests/BUILD b/tensorflow/contrib/data/python/kernel_tests/BUILD index f70b29c43b..8cfe4a727a 100644 --- a/tensorflow/contrib/data/python/kernel_tests/BUILD +++ b/tensorflow/contrib/data/python/kernel_tests/BUILD @@ -479,6 +479,10 @@ py_test( size = "small", srcs = ["prefetching_ops_test.py"], srcs_version = "PY2AND3", + tags = [ + "manual", + "no_oss", + ], deps = [ "//tensorflow/contrib/data/python/ops:prefetching_ops", "//tensorflow/core:protos_all_py", -- GitLab From a5a1e9e43131b387395930f38234fc10b02d874b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 08:52:53 -0700 Subject: [PATCH 204/906] Updated test (but not source) of https://www.tensorflow.org/api_docs/python/tf/contrib/training/HParams to show that it allows '=' in the values. PiperOrigin-RevId: 190470578 --- .../training/python/training/hparam_test.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/tensorflow/contrib/training/python/training/hparam_test.py b/tensorflow/contrib/training/python/training/hparam_test.py index 16397622ed..96eff86d8d 100644 --- a/tensorflow/contrib/training/python/training/hparam_test.py +++ b/tensorflow/contrib/training/python/training/hparam_test.py @@ -38,40 +38,60 @@ class HParamsTest(test.TestCase): self.assertFalse('bar' in hparams) def testSomeValues(self): - hparams = hparam.HParams(aaa=1, b=2.0, c_c='relu6') - self.assertDictEqual({'aaa': 1, 'b': 2.0, 'c_c': 'relu6'}, hparams.values()) - expected_str = '[(\'aaa\', 1), (\'b\', 2.0), (\'c_c\', \'relu6\')]' + hparams = hparam.HParams(aaa=1, b=2.0, c_c='relu6', d='/a/b=c/d') + self.assertDictEqual( + {'aaa': 1, 'b': 2.0, 'c_c': 'relu6', 'd': '/a/b=c/d'}, + hparams.values()) + expected_str = ('[(\'aaa\', 1), (\'b\', 2.0), (\'c_c\', \'relu6\'), ' + '(\'d\', \'/a/b=c/d\')]') self.assertEqual(expected_str, str(hparams.__str__())) self.assertEqual(expected_str, str(hparams)) self.assertEqual(1, hparams.aaa) self.assertEqual(2.0, hparams.b) self.assertEqual('relu6', hparams.c_c) + self.assertEqual('/a/b=c/d', hparams.d) hparams.parse('aaa=12') self.assertDictEqual({ 'aaa': 12, 'b': 2.0, - 'c_c': 'relu6' + 'c_c': 'relu6', + 'd': '/a/b=c/d' }, hparams.values()) self.assertEqual(12, hparams.aaa) self.assertEqual(2.0, hparams.b) self.assertEqual('relu6', hparams.c_c) + self.assertEqual('/a/b=c/d', hparams.d) hparams.parse('c_c=relu4, b=-2.0e10') self.assertDictEqual({ 'aaa': 12, 'b': -2.0e10, - 'c_c': 'relu4' + 'c_c': 'relu4', + 'd': '/a/b=c/d' }, hparams.values()) self.assertEqual(12, hparams.aaa) self.assertEqual(-2.0e10, hparams.b) self.assertEqual('relu4', hparams.c_c) + self.assertEqual('/a/b=c/d', hparams.d) hparams.parse('c_c=,b=0,') - self.assertDictEqual({'aaa': 12, 'b': 0, 'c_c': ''}, hparams.values()) + self.assertDictEqual({'aaa': 12, 'b': 0, 'c_c': '', 'd': '/a/b=c/d'}, + hparams.values()) self.assertEqual(12, hparams.aaa) self.assertEqual(0.0, hparams.b) self.assertEqual('', hparams.c_c) + self.assertEqual('/a/b=c/d', hparams.d) hparams.parse('c_c=2.3",b=+2,') self.assertEqual(2.0, hparams.b) self.assertEqual('2.3"', hparams.c_c) + hparams.parse('d=/a/b/c/d,aaa=11,') + self.assertEqual(11, hparams.aaa) + self.assertEqual(2.0, hparams.b) + self.assertEqual('2.3"', hparams.c_c) + self.assertEqual('/a/b/c/d', hparams.d) + hparams.parse('b=1.5,d=/a=b/c/d,aaa=10,') + self.assertEqual(10, hparams.aaa) + self.assertEqual(1.5, hparams.b) + self.assertEqual('2.3"', hparams.c_c) + self.assertEqual('/a=b/c/d', hparams.d) with self.assertRaisesRegexp(ValueError, 'Unknown hyperparameter'): hparams.parse('x=123') with self.assertRaisesRegexp(ValueError, 'Could not parse'): @@ -84,17 +104,19 @@ class HParamsTest(test.TestCase): hparams.parse('b=relu') with self.assertRaisesRegexp(ValueError, 'Must not pass a list'): hparams.parse('aaa=[123]') - self.assertEqual(12, hparams.aaa) - self.assertEqual(2.0, hparams.b) + self.assertEqual(10, hparams.aaa) + self.assertEqual(1.5, hparams.b) self.assertEqual('2.3"', hparams.c_c) + self.assertEqual('/a=b/c/d', hparams.d) # Exports to proto. hparam_def = hparams.to_proto() # Imports from proto. hparams2 = hparam.HParams(hparam_def=hparam_def) # Verifies that all hparams are restored. - self.assertEqual(12, hparams2.aaa) - self.assertEqual(2.0, hparams2.b) + self.assertEqual(10, hparams2.aaa) + self.assertEqual(1.5, hparams2.b) self.assertEqual('2.3"', hparams2.c_c) + self.assertEqual('/a=b/c/d', hparams2.d) def testSetFromMap(self): hparams = hparam.HParams(a=1, b=2.0, c='tanh') -- GitLab From 2b078a508b8c6c920db121f676650d7972749bd7 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 10:00:20 -0700 Subject: [PATCH 205/906] Automated g4 rollback of changelist 190293303 PiperOrigin-RevId: 190479555 --- tensorflow/c/BUILD | 2 -- tensorflow/c/python_api.cc | 26 --------------- tensorflow/c/python_api.h | 7 ---- tensorflow/python/BUILD | 2 -- tensorflow/python/client/tf_session.i | 1 - tensorflow/python/framework/importer_test.py | 34 -------------------- tensorflow/python/framework/ops.py | 10 ------ 7 files changed, 82 deletions(-) diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index f4a486d330..d096647558 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -279,8 +279,6 @@ tf_cuda_library( deps = [ ":c_api", ":c_api_internal", - # TODO(b/74620627): remove when _USE_C_SHAPES is removed - "//tensorflow/python:cpp_shape_inference_proto_cc", ], ) diff --git a/tensorflow/c/python_api.cc b/tensorflow/c/python_api.cc index 93155998b8..cd604538f1 100644 --- a/tensorflow/c/python_api.cc +++ b/tensorflow/c/python_api.cc @@ -16,7 +16,6 @@ limitations under the License. #include "tensorflow/c/python_api.h" #include "tensorflow/c/c_api_internal.h" -#include "tensorflow/python/framework/cpp_shape_inference.pb.h" namespace tensorflow { @@ -110,29 +109,4 @@ void ExtendSession(TF_Session* session, TF_Status* status) { session->extend_before_run = false; } -std::string ResourceHandleShapeAndType(TF_Graph* graph, TF_Output output) { - Node* node = &output.oper->node; - CppShapeInferenceResult::HandleData handle_data; - handle_data.set_is_set(true); - { - mutex_lock l(graph->mu); - tensorflow::shape_inference::InferenceContext* ic = - graph->refiner.GetContext(node); - CHECK(ic != nullptr); - CHECK_LT(output.index, ic->num_outputs()); - const auto* shapes_and_types = - ic->output_handle_shapes_and_types(output.index); - if (shapes_and_types == nullptr) return ""; - - for (const auto& p : *shapes_and_types) { - auto* out_shape_and_type = handle_data.add_shape_and_type(); - ic->ShapeHandleToProto(p.shape, out_shape_and_type->mutable_shape()); - out_shape_and_type->set_dtype(p.dtype); - } - } - string result; - handle_data.SerializeToString(&result); - return result; -} - } // namespace tensorflow diff --git a/tensorflow/c/python_api.h b/tensorflow/c/python_api.h index 2d4c8cd9ed..13b680b3a2 100644 --- a/tensorflow/c/python_api.h +++ b/tensorflow/c/python_api.h @@ -16,8 +16,6 @@ limitations under the License. #ifndef TENSORFLOW_C_PYTHON_API_H_ #define TENSORFLOW_C_PYTHON_API_H_ -#include - #include "tensorflow/c/c_api.h" // These functions can be removed without notice. They exist to facilitate some @@ -53,11 +51,6 @@ void SetRequireShapeInferenceFns(TF_Graph* graph, bool require); // the graph after the session has been made aware of them. void ExtendSession(TF_Session* session, TF_Status* status); -// Returns the serialized CppShapeInferenceResult::HandleData proto for -// `output` if its a resource tensor, or otherwise returns the empty string. -// TODO(b/74620627): remove when _USE_C_SHAPES is removed -std::string ResourceHandleShapeAndType(TF_Graph* graph, TF_Output output); - } // namespace tensorflow #endif // TENSORFLOW_C_PYTHON_API_H_ diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index e6ad564ede..30ecc477f2 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -3131,8 +3131,6 @@ tf_proto_library( srcs = ["framework/cpp_shape_inference.proto"], cc_api_version = 2, protodeps = tf_additional_all_protos(), - # TODO(b/74620627): remove when _USE_C_SHAPES is removed - visibility = ["//tensorflow:internal"], ) py_test( diff --git a/tensorflow/python/client/tf_session.i b/tensorflow/python/client/tf_session.i index 70a3d032f4..e88fc0c01a 100644 --- a/tensorflow/python/client/tf_session.i +++ b/tensorflow/python/client/tf_session.i @@ -723,7 +723,6 @@ def TF_Reset(target, containers=None, config=None): %unignore TF_TryEvaluateConstant_wrapper; %noexception TF_TryEvaluateConstant_wrapper; %unignore ExtendSession; -%unignore ResourceHandleShapeAndType; %include "tensorflow/python/client/tf_session_helper.h" diff --git a/tensorflow/python/framework/importer_test.py b/tensorflow/python/framework/importer_test.py index 369669c2e6..6593b17184 100644 --- a/tensorflow/python/framework/importer_test.py +++ b/tensorflow/python/framework/importer_test.py @@ -39,7 +39,6 @@ from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn_ops from tensorflow.python.ops import random_ops -from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variables import tensorflow.python.ops.nn_grad # pylint: disable=unused-import from tensorflow.python.platform import test @@ -357,39 +356,6 @@ class ImportGraphDefTest(test.TestCase): self.assertEqual(d._input_types, [dtypes.int32_ref, dtypes.int32]) self.assertEqual(d.outputs, []) - def testResources(self): - # Produce GraphDef containing a ops producing and consuming resources. - graph = ops.Graph() - with graph.as_default(): - var = resource_variable_ops.ResourceVariable(1.0) - var_assign = var.assign(2.0) - # Use an op that requires handle shape to be set. - var_shape = resource_variable_ops.variable_shape(var.handle) - init = variables.global_variables_initializer() - graph_def = graph.as_graph_def() - - # Import the GraphDef. - with ops.Graph().as_default(): - # pylint: disable=unused-variable - imported_var, imported_assign, imported_shape, imported_init = ( - importer.import_graph_def( - graph_def, - return_elements=[var.name, var_assign.name, var_shape.name, - init.name])) - - # Make sure the handle shape is set on the imported variable. - new_var_shape = resource_variable_ops.variable_shape(imported_var) - # pylint: enable=unused-variable - - # Run the imported graph. - # TODO(b/76173421): make this work (currently DCHECKS) - # with self.test_session() as sess: - # sess.run(imported_init) - # self.assertEqual(sess.run(imported_var), 1.0) - # self.assertEqual(sess.run(imported_assign), 2.0) - # self.assertEqual(list(sess.run(imported_shape)), []) - # self.assertEqual(list(sess.run(new_var_shape)), []) - def testWhileLoop(self): # Produce GraphDef containing while loop. graph = ops.Graph() diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index 5e4a884a70..e579289a8d 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -42,7 +42,6 @@ from tensorflow.python.eager import context from tensorflow.python.eager import core from tensorflow.python.eager import tape from tensorflow.python.framework import c_api_util -from tensorflow.python.framework import cpp_shape_inference_pb2 from tensorflow.python.framework import device as pydev from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors @@ -296,7 +295,6 @@ class Tensor(_TensorLike): # Attributes used for C++ shape inference. Not inspected, only forwarded. # If set, will be a HandleData object from cpp_shape_inference.proto. - # TODO(b/74620627): remove when _USE_C_SHAPES is removed self._handle_data = None self._id = uid() @@ -2474,14 +2472,6 @@ def _set_shapes_for_outputs_c_api(op): shape_vector = [None if d == -1 else d for d in shape_vector] output.set_shape(tensor_shape.TensorShape(shape_vector)) - serialized = c_api.ResourceHandleShapeAndType(op._graph._c_graph, - output._as_tf_output()) - if serialized: - output._handle_data = ( - cpp_shape_inference_pb2.CppShapeInferenceResult.HandleData.FromString( - compat.as_bytes(serialized))) - else: - output._handle_data = None # TODO(skyewm): remove this when _USE_C_API flag is removed. def _set_shapes_for_outputs(op): -- GitLab From cc6b2ae837e9c0ce3678671ff5bd59f0f8e53e06 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Mon, 26 Mar 2018 10:25:46 -0700 Subject: [PATCH 206/906] Adding a FunctionBufferingResourceReset Op that resets the state of the function buffering resource so that we can start using it with re-initializable iterators. PiperOrigin-RevId: 190484110 --- .../data/kernels/prefetching_kernels.cc | 66 +++++++-- tensorflow/contrib/data/ops/dataset_ops.cc | 9 ++ .../kernel_tests/prefetching_ops_test.py | 137 +++++++++++++++--- .../data/python/ops/prefetching_ops.py | 5 + 4 files changed, 184 insertions(+), 33 deletions(-) diff --git a/tensorflow/contrib/data/kernels/prefetching_kernels.cc b/tensorflow/contrib/data/kernels/prefetching_kernels.cc index 190a6ee580..79d1fc3494 100644 --- a/tensorflow/contrib/data/kernels/prefetching_kernels.cc +++ b/tensorflow/contrib/data/kernels/prefetching_kernels.cc @@ -65,12 +65,6 @@ class FunctionBufferingResource : public ResourceBase { ~FunctionBufferingResource() override { Cancel(); - { - mutex_lock l(mu_); - while (is_buffering_) { - cond_var_.wait(l); - } - } if (thread_pool_ != nullptr) { delete thread_pool_; } @@ -107,6 +101,20 @@ class FunctionBufferingResource : public ResourceBase { void Cancel() LOCKS_EXCLUDED(mu_) { mutex_lock l(mu_); cancelled_ = true; + while (is_buffering_) { + cond_var_.wait(l); + } + } + + // Cancels all pending operations and then clears out the state. + void Reset() LOCKS_EXCLUDED(mu_) { + Cancel(); + mutex_lock l(mu_); + buffer_.clear(); + requests_.clear(); + is_buffering_ = false; + end_of_sequence_ = false; + cancelled_ = false; } // If the buffer has anything, runs `callback` on the first element in the @@ -200,13 +208,12 @@ class FunctionBufferingResource : public ResourceBase { mutex_lock l(mu_); BufferElement buffer_element; buffer_element.status = status; - if (!status.ok()) { + if (status.ok()) { + buffer_element.value.swap(*rets); + } else { end_of_sequence_ = true; is_buffering_ = false; - buffer_.push_back(std::move(buffer_element)); - return; } - buffer_element.value.swap(*rets); buffer_.push_back(std::move(buffer_element)); if (!requests_.empty()) { buffer_front = std::move(buffer_.front()); @@ -214,7 +221,7 @@ class FunctionBufferingResource : public ResourceBase { callback = std::move(requests_.front()); requests_.pop_front(); } - if (buffer_.size() < buffer_size_) { + if (buffer_.size() < buffer_size_ && !end_of_sequence_) { restart_buffering = true; } else { is_buffering_ = false; @@ -406,6 +413,43 @@ REGISTER_KERNEL_BUILDER(Name("FunctionBufferingResourceGetNext") FunctionBufferingResourceGetNextOp); #endif // TENSORFLOW_USE_SYCL +// Resets the FunctionBufferingResource, cancelling all pending requests and +// clearing out the buffer. +class FunctionBufferingResourceResetOp : public OpKernel { + public: + explicit FunctionBufferingResourceResetOp(OpKernelConstruction* ctx) + : OpKernel(ctx) {} + + ~FunctionBufferingResourceResetOp() override {} + + void Compute(OpKernelContext* ctx) override { + ResourceHandle handle; + OP_REQUIRES_OK(ctx, + HandleFromInput(ctx, "function_buffer_resource", &handle)); + FunctionBufferingResource* buffer = nullptr; + OP_REQUIRES_OK( + ctx, LookupResource(ctx, handle, &buffer)); + core::ScopedUnref s(buffer); + + buffer->Reset(); + } +}; + +REGISTER_KERNEL_BUILDER(Name("FunctionBufferingResourceReset") + .Device(DEVICE_CPU) + .HostMemory("function_buffer_resource"), + FunctionBufferingResourceResetOp); +REGISTER_KERNEL_BUILDER(Name("FunctionBufferingResourceReset") + .Device(DEVICE_GPU) + .HostMemory("function_buffer_resource"), + FunctionBufferingResourceResetOp); +#if TENSORFLOW_USE_SYCL +REGISTER_KERNEL_BUILDER(Name("FunctionBufferingResourceReset") + .Device(DEVICE_SYCL) + .HostMemory("function_buffer_resource"), + FunctionBufferingResourceResetOp); +#endif // TENSORFLOW_USE_SYCL + class IteratorGetDeviceOp : public OpKernel { public: using OpKernel::OpKernel; diff --git a/tensorflow/contrib/data/ops/dataset_ops.cc b/tensorflow/contrib/data/ops/dataset_ops.cc index 74737bbcad..bd96448d64 100644 --- a/tensorflow/contrib/data/ops/dataset_ops.cc +++ b/tensorflow/contrib/data/ops/dataset_ops.cc @@ -83,6 +83,15 @@ output: A list of return values. output_types: The type list for the return values. )doc"); +REGISTER_OP("FunctionBufferingResourceReset") + .Input("function_buffer_resource: resource") + .SetShapeFn(shape_inference::UnknownShape) + .Doc(R"doc( +Resets the FunctionBufferingResource. + +function_buffer_resource: The FunctionBufferingResource handle. +)doc"); + REGISTER_OP("ThreadPoolDataset") .Input("input_dataset: variant") .Input("thread_pool: resource") diff --git a/tensorflow/contrib/data/python/kernel_tests/prefetching_ops_test.py b/tensorflow/contrib/data/python/kernel_tests/prefetching_ops_test.py index 1d74afe1e1..a14736ac09 100644 --- a/tensorflow/contrib/data/python/kernel_tests/prefetching_ops_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/prefetching_ops_test.py @@ -17,7 +17,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import itertools import threading from tensorflow.contrib.data.python.ops import prefetching_ops @@ -39,25 +38,29 @@ class StagingAreaOpsTest(test.TestCase): def setUp(self): self._event = threading.Event() - def _prefetch_fn_helper(self, buffer_name, device0, device1): - worker_config = config_pb2.ConfigProto() - worker_config.device_count["CPU"] = 2 + def _create_ds_and_iterator(self, device0, initializable=False): def gen(): - for i in itertools.count(start=1, step=1): - yield [i + 0.0] + for i in range(1, 10): + yield [float(i)] if i == 6: self._event.set() with ops.device(device0): - dataset_3 = dataset_ops.Dataset.from_generator(gen, (dtypes.float32)) - iterator_3 = dataset_3.make_one_shot_iterator() - iterator_3_handle = iterator_3.string_handle() + ds = dataset_ops.Dataset.from_generator(gen, (dtypes.float32)) + if initializable: + ds_iterator = ds.make_initializable_iterator() + else: + ds_iterator = ds.make_one_shot_iterator() + return (ds, ds_iterator) + + def _create_ops(self, ds, ds_iterator, buffer_name, device0, device1): + ds_iterator_handle = ds_iterator.string_handle() @function.Defun(dtypes.string) def _remote_fn(h): remote_iterator = iterator_ops.Iterator.from_string_handle( - h, dataset_3.output_types, dataset_3.output_shapes) + h, ds.output_types, ds.output_shapes) return remote_iterator.get_next() target = constant_op.constant(device0) @@ -65,7 +68,7 @@ class StagingAreaOpsTest(test.TestCase): buffer_resource_handle = prefetching_ops.function_buffering_resource( f=_remote_fn, target_device=target, - string_arg=iterator_3_handle, + string_arg=ds_iterator_handle, buffer_size=3, thread_pool_size=2, shared_name=buffer_name) @@ -74,6 +77,20 @@ class StagingAreaOpsTest(test.TestCase): prefetch_op = prefetching_ops.function_buffering_resource_get_next( function_buffer_resource=buffer_resource_handle, output_types=[dtypes.float32]) + reset_op = prefetching_ops.function_buffering_resource_reset( + function_buffer_resource=buffer_resource_handle) + destroy_op = resource_variable_ops.destroy_resource_op( + buffer_resource_handle, ignore_lookup_error=True) + + return (prefetch_op, reset_op, destroy_op) + + def _prefetch_fn_helper_one_shot(self, buffer_name, device0, device1): + worker_config = config_pb2.ConfigProto() + worker_config.device_count["CPU"] = 2 + + ds, ds_iterator = self._create_ds_and_iterator(device0, initializable=False) + prefetch_op, _, destroy_op = self._create_ops(ds, ds_iterator, buffer_name, + device0, device1) with self.test_session(config=worker_config) as sess: elem = sess.run(prefetch_op) @@ -87,26 +104,102 @@ class StagingAreaOpsTest(test.TestCase): self._event.wait() elem = sess.run(prefetch_op) self.assertEqual(elem, [5.0]) - sess.run( - resource_variable_ops.destroy_resource_op( - buffer_resource_handle, ignore_lookup_error=True)) + sess.run(destroy_op) def testSameDeviceCPU(self): - self._prefetch_fn_helper("same_device_cpu", - "/job:localhost/replica:0/task:0/cpu:0", - "/job:localhost/replica:0/task:0/cpu:0") + self._prefetch_fn_helper_one_shot("same_device_cpu", + "/job:localhost/replica:0/task:0/cpu:0", + "/job:localhost/replica:0/task:0/cpu:0") def testDifferentDeviceCPU(self): - self._prefetch_fn_helper("diff_device_cpu", - "/job:localhost/replica:0/task:0/cpu:0", - "/job:localhost/replica:0/task:0/cpu:1") + self._prefetch_fn_helper_one_shot("diff_device_cpu", + "/job:localhost/replica:0/task:0/cpu:0", + "/job:localhost/replica:0/task:0/cpu:1") def testDifferentDeviceCPUGPU(self): if not test_util.is_gpu_available(): self.skipTest("No GPU available") - self._prefetch_fn_helper("cpu_gpu", "/job:localhost/replica:0/task:0/cpu:0", - "/job:localhost/replica:0/task:0/gpu:0") + self._prefetch_fn_helper_one_shot("cpu_gpu", + "/job:localhost/replica:0/task:0/cpu:0", + "/job:localhost/replica:0/task:0/gpu:0") + + def testReinitialization(self): + worker_config = config_pb2.ConfigProto() + worker_config.device_count["CPU"] = 2 + + device0 = "/job:localhost/replica:0/task:0/cpu:0" + device1 = "/job:localhost/replica:0/task:0/cpu:1" + ds, ds_iterator = self._create_ds_and_iterator(device0, initializable=True) + prefetch_op, reset_op, destroy_op = self._create_ops( + ds, ds_iterator, "reinit", device0, device1) + + with self.test_session(config=worker_config) as sess: + sess.run(ds_iterator.initializer) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [1.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [2.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [3.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [4.0]) + self._event.wait() + elem = sess.run(prefetch_op) + self.assertEqual(elem, [5.0]) + # Lets reset the function buffering resource and reinitialize the + # iterator. Should be able to go through this again. + self._event.clear() + sess.run(reset_op) + sess.run(ds_iterator.initializer) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [1.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [2.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [3.0]) + elem = sess.run(prefetch_op) + self.assertEqual(elem, [4.0]) + self._event.wait() + elem = sess.run(prefetch_op) + self.assertEqual(elem, [5.0]) + sess.run(destroy_op) + + def testReinitializationOutOfRange(self): + worker_config = config_pb2.ConfigProto() + worker_config.device_count["CPU"] = 2 + + device0 = "/job:localhost/replica:0/task:0/cpu:0" + device1 = "/job:localhost/replica:0/task:0/cpu:1" + ds, ds_iterator = self._create_ds_and_iterator(device0, initializable=True) + prefetch_op, reset_op, destroy_op = self._create_ops( + ds, ds_iterator, "reinit", device0, device1) + + with self.test_session(config=worker_config) as sess: + sess.run(ds_iterator.initializer) + for i in range(1, 10): + elem = sess.run(prefetch_op) + self.assertEqual(elem, [float(i)]) + # Try fetching after its over twice to test out end of sequence. + with self.assertRaises(errors.OutOfRangeError): + sess.run(prefetch_op) + with self.assertRaises(errors.OutOfRangeError): + sess.run(prefetch_op) + + # Now reset everything and try it out again. + self._event.clear() + sess.run(reset_op) + sess.run(ds_iterator.initializer) + for i in range(1, 10): + elem = sess.run(prefetch_op) + self.assertEqual(elem, [float(i)]) + # Try fetching after its over twice to test out end of sequence. + with self.assertRaises(errors.OutOfRangeError): + sess.run(prefetch_op) + with self.assertRaises(errors.OutOfRangeError): + sess.run(prefetch_op) + + sess.run(destroy_op) def testPrefetchToDevice(self): host_dataset = dataset_ops.Dataset.range(10) diff --git a/tensorflow/contrib/data/python/ops/prefetching_ops.py b/tensorflow/contrib/data/python/ops/prefetching_ops.py index e38d53a221..1438b5426f 100644 --- a/tensorflow/contrib/data/python/ops/prefetching_ops.py +++ b/tensorflow/contrib/data/python/ops/prefetching_ops.py @@ -62,6 +62,11 @@ def function_buffering_resource_get_next(function_buffer_resource, name=name) +def function_buffering_resource_reset(function_buffer_resource, name=None): + return gen_dataset_ops.function_buffering_resource_reset( + function_buffer_resource=function_buffer_resource, name=name) + + # pylint: disable=protected-access class _PrefetchToDeviceIterator(object): """A replacement for @{tf.data.Iterator} that prefetches to another device.""" -- GitLab From be917027e37c5e8f21f6ba07f24bdbf072cf6dfd Mon Sep 17 00:00:00 2001 From: Mingsheng Hong Date: Mon, 26 Mar 2018 10:51:21 -0700 Subject: [PATCH 207/906] Added experimental C APIs to build a stack of dataset + iterator nodes that reads imagenet TFRecord files. PiperOrigin-RevId: 190488817 --- tensorflow/c/BUILD | 2 + tensorflow/c/c_api_experimental.cc | 7218 ++++++++++++++++++++++- tensorflow/c/c_api_experimental.h | 31 +- tensorflow/c/c_api_experimental_test.cc | 84 +- tensorflow/c/testdata/tf_record | Bin 0 -> 417114 bytes 5 files changed, 7155 insertions(+), 180 deletions(-) create mode 100644 tensorflow/c/testdata/tf_record diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index d096647558..426f97b844 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -220,6 +220,7 @@ tf_cc_test( name = "c_api_experimental_test", size = "small", srcs = ["c_api_experimental_test.cc"], + data = ["testdata/tf_record"], linkopts = select({ "//tensorflow:darwin": ["-headerpad_max_install_names"], "//conditions:default": [], @@ -230,6 +231,7 @@ tf_cc_test( deps = [ ":c_api_experimental", ":c_test_util", + "//tensorflow/core:lib", "//tensorflow/core:test", "//tensorflow/core:test_main", ], diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc index 8593a8eb50..1c809cb21e 100644 --- a/tensorflow/c/c_api_experimental.cc +++ b/tensorflow/c/c_api_experimental.cc @@ -22,10 +22,15 @@ limitations under the License. #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/protobuf/config.pb.h" +using tensorflow::FunctionDef; using tensorflow::Node; using tensorflow::NodeBuilder; using tensorflow::Status; -using tensorflow::Tensor; + +namespace { +typedef std::unique_ptr + UniqueFuncPtr; +} // struct TF_Operation { tensorflow::Node node; }; static TF_Operation* ToTF_Operation(Node* node) { @@ -102,8 +107,7 @@ void TF_ShutdownTPU(TF_Session* session, TF_Status* status) { /*run_metadata*/ nullptr, status); } -TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, - size_t* len) { +const char* TF_GraphDebugString(TF_Graph* graph, size_t* len) { tensorflow::mutex_lock c(graph->mu); const auto& debug_str = graph->graph.ToGraphDefDebug().DebugString(); *len = debug_str.size(); @@ -112,55 +116,56 @@ TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, return ret; } -// TODO(hongm): Replace this will a real implementation. -static tensorflow::Status BuildDatasetTest(TF_Graph* dataset_graph, - Node** dataset_node) { - tensorflow::mutex_lock c(dataset_graph->mu); - Tensor const_t(tensorflow::DT_INT32, tensorflow::TensorShape({})); - const_t.flat()(0) = 1; - - Node* const_node; - TF_RETURN_IF_ERROR(NodeBuilder("Const", "Const") - .Attr("dtype", tensorflow::DT_INT32) - .Attr("value", const_t) - .Finalize(&dataset_graph->graph, &const_node)); - - std::vector input_list; - input_list.push_back(NodeBuilder::NodeOut(const_node, 0)); - - return NodeBuilder("TensorDataset", "TensorDataset") - .Input(input_list) - .Attr("Toutput_types", {tensorflow::DT_INT32}) - .Attr("output_shapes", {tensorflow::TensorShapeProto()}) - .Finalize(&dataset_graph->graph, dataset_node); -} - -// On success, returns a newly created TF_Function instance from -// `text_proto`. It must be deleted by calling TF_DeleteFunction. -static TF_Function* CreateFunctionFromTextProto(const char* text_proto, - TF_Status* status) { - tensorflow::FunctionDef fdef; - if (!tensorflow::protobuf::TextFormat::ParseFromString(text_proto, &fdef)) { +// On success, returns a set of TF_Function instances from `text_proto` of +// GraphDef type. These functions must be deleted by calling TF_DeleteFunction. +// +// If `mutate_proto_func` is non-NULL, run it over each FunctionDef proto, +// before creating a TF_Function out of the possibly mutated proto. +static std::vector CreateFunctionsFromTextProto( + const char* text_proto, + std::function* mutate_proto_func, TF_Status* status) { + tensorflow::GraphDef gdef; + if (!tensorflow::protobuf::TextFormat::ParseFromString(text_proto, &gdef)) { status->status = tensorflow::errors::Internal( - "Invalid text proto for FunctionDef: ", text_proto); - return nullptr; + "Invalid text proto for GraphDef: ", text_proto); + return {}; + } + const auto& fdef_lib = gdef.library(); + if (fdef_lib.gradient_size() > 0) { + status->status = tensorflow::errors::Internal( + "GradientDef is not supported in reading Dataset related functions: ", + text_proto); + return {}; } - std::vector binary_proto_buf(fdef.ByteSizeLong()); - fdef.SerializeToArray(binary_proto_buf.data(), binary_proto_buf.size()); - return TF_FunctionImportFunctionDef(binary_proto_buf.data(), - binary_proto_buf.size(), status); + std::vector ret; + for (const auto& fdef : fdef_lib.function()) { + // Make a copy so that we can mutate it. + FunctionDef fdef_to_load = fdef; + if (mutate_proto_func) { + (*mutate_proto_func)(&fdef_to_load); + } + VLOG(1) << "Adding func to graph: " << fdef_to_load.DebugString(); + std::vector binary_proto_buf(fdef_to_load.ByteSizeLong()); + fdef_to_load.SerializeToArray(binary_proto_buf.data(), + binary_proto_buf.size()); + auto func = TF_FunctionImportFunctionDef(binary_proto_buf.data(), + binary_proto_buf.size(), status); + if (!status->status.ok()) return {}; + ret.push_back(UniqueFuncPtr(func, TF_DeleteFunction)); + } + return ret; } -// On success, returns a newly created TF_Function instance from `proto_file`, -// and sets `dataset_name` to the created dataset name. The returned function -// must be deleted by calling TF_DeleteFunction. -// -// TODO(hongm): Support reading the file given by `proto_file`. -static TF_Function* LoadDatasetFunction(const char* proto_file, - std::string* dataset_name, - TF_Status* status) { +// On success, returns a newly created TF_Function instance encoding a dataset +// node stack that returns a sequence of 3 floats, and sets `dataset_name` to +// the created dataset name. The returned function must be deleted by calling +// TF_DeleteFunction. +static UniqueFuncPtr CreateFakeDatasetFunction(std::string* dataset_name, + TF_Status* status) { const char* func_def = R"PREFIX( -signature { +library { + function { + signature { name: "_make_dataset_d8de2712" output_arg { name: "TensorSliceDataset" @@ -217,112 +222,7029 @@ signature { ret { key: "TensorSliceDataset" value: "TensorSliceDataset:handle:0" - })PREFIX"; + } + } +} +)PREFIX"; *dataset_name = "_make_dataset_d8de2712"; - return CreateFunctionFromTextProto(func_def, status); + auto functions = CreateFunctionsFromTextProto( + func_def, /*mutate_proto_func*/ nullptr, status); + DCHECK_EQ(functions.size(), 1); + return std::move(functions[0]); } -// TODO(hongm): Use `file_path` in the implementation. -TF_Operation* TF_MakeIteratorGetNextWithDatasets(TF_Graph* graph, - const char* file_path, - TF_Function** dataset_func, - TF_Status* status) { - tensorflow::Status s; - - // We can parameterize the function name, if we ever need more than 1 - // iterators in a graph. - const std::string dataset_name = "UNIQUE_DATASET"; - - std::unique_ptr dataset_graph( - TF_NewGraph(), TF_DeleteGraph); - Node* dataset_node = nullptr; - s = BuildDatasetTest(dataset_graph.get(), &dataset_node); - if (!s.ok()) { - status->status = s; - return nullptr; - } - - TF_Output output{ToTF_Operation(dataset_node), 0}; - std::unique_ptr result_func( - TF_GraphToFunction(dataset_graph.get(), dataset_name.c_str(), - /*append_hash_to_fn_name*/ false, - /*num_opers*/ -1, - /*opers*/ nullptr, - /*numinputs*/ 0, - /*inputs*/ nullptr, - /*noutputs*/ 1, - /*outputs*/ &output, - /*outputnames*/ nullptr, - /*functionoptions*/ nullptr, "", status), - TF_DeleteFunction); - if (!status->status.ok()) { - return nullptr; - } - - TF_GraphCopyFunction(graph, result_func.get(), /*gradient*/ nullptr, status); - - if (!status->status.ok()) { - return nullptr; - } - - tensorflow::mutex_lock c(graph->mu); - - tensorflow::NameAttrList func; - func.set_name(dataset_name); - // Run the iterator node on CPU. - Node* oneshot_iterator_node; - std::vector output_shape_list; - output_shape_list.push_back(tensorflow::TensorShapeProto()); - s = NodeBuilder("OneShotIterator", "OneShotIterator") - .Device("/device:CPU:0") - .Attr("container", "") - .Attr("dataset_factory", func) - .Attr("output_types", {tensorflow::DT_INT32}) - .Attr("output_shapes", output_shape_list) - .Attr("shared_name", "") - .Finalize(&graph->graph, &oneshot_iterator_node); - if (!s.ok()) { - status->status = s; - return nullptr; - } - // Run shape inference function for each newly added node, so that more - // subsequent nodes can be added to the graph via C API (TF_NewOperation()). - s = graph->refiner.AddNode(oneshot_iterator_node); - if (!s.ok()) { - status->status = s; - return nullptr; - } - - // Run the iterator node on CPU. - Node* getnext_node; - s = NodeBuilder("IteratorGetNext", "IteratorGetNext") - .Input(oneshot_iterator_node) - .Device("/device:CPU:0") - .Attr("output_types", {tensorflow::DT_INT32}) - .Attr("output_shapes", output_shape_list) - .Finalize(&graph->graph, &getnext_node); - if (!s.ok()) { - status->status = s; - return nullptr; +// On success, returns a set of TF_Function instances encoding a dataset +// node stack that reads a Imagenet TFRecordFile dataset from `file_path`, and +// sets `dataset_name` to the created dataset name. The returned functions must +// be deleted by calling TF_DeleteFunction. +static std::vector CreateImagenetDatasetFunctions( + const char* file_path, std::string* dataset_name, TF_Status* status) { + const char* func_def = R"PREFIX( +library { + function { + signature { + name: "tf_map_func_91295dea" + input_arg { + name: "arg0" + type: DT_STRING + } + output_arg { + name: "FlatMapDataset" + type: DT_VARIANT + } + description: "A wrapper for Defun that facilitates shape inference." + is_stateful: true + } + node_def { + name: "flat_filenames/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: -1 + } + } + } + } + node_def { + name: "flat_filenames" + op: "Reshape" + input: "arg0" + input: "flat_filenames/shape:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "TensorSliceDataset" + op: "TensorSliceDataset" + input: "flat_filenames:output:0" + attr { + key: "Toutput_types" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + } + node_def { + name: "FlatMapDataset" + op: "FlatMapDataset" + input: "TensorSliceDataset:handle:0" + attr { + key: "Targuments" + value { + list { + } + } + } + attr { + key: "f" + value { + func { + name: "tf_map_func_0cc8c35b" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + } + ret { + key: "FlatMapDataset" + value: "FlatMapDataset:handle:0" + } } - // Run shape inference function for each newly added node, so that more - // subsequent nodes can be added to the graph via C API (TF_NewOperation()). - s = graph->refiner.AddNode(getnext_node); - if (!s.ok()) { - status->status = s; - return nullptr; + function { + signature { + name: "tf_map_func_0cc8c35b" + input_arg { + name: "arg0" + type: DT_STRING + } + output_arg { + name: "TFRecordDataset" + type: DT_VARIANT + } + description: "A wrapper for Defun that facilitates shape inference." + is_stateful: true + } + node_def { + name: "compression_type" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "" + } + } + } + } + node_def { + name: "buffer_size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 8388608 + } + } + } + } + node_def { + name: "TFRecordDataset" + op: "TFRecordDataset" + input: "arg0" + input: "compression_type:output:0" + input: "buffer_size:output:0" + } + ret { + key: "TFRecordDataset" + value: "TFRecordDataset:handle:0" + } } + function { + signature { + name: "tf_map_func_74b6b15c" + input_arg { + name: "arg0" + type: DT_STRING + } + output_arg { + name: "Reshape_1" + type: DT_FLOAT + } + output_arg { + name: "sub_1" + type: DT_INT32 + } + description: "A wrapper for Defun that facilitates shape inference." + is_stateful: true + } + node_def { + name: "ParseSingleExample/key_image/class/label" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: -1 + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape" + op: "Reshape" + input: "ParseSingleExample/key_image/class/label:output:0" + input: "ParseSingleExample/Reshape/shape:output:0" + attr { + key: "T" + value { + type: DT_INT64 + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "ParseSingleExample/key_image/class/text" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "" + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_1/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_1" + op: "Reshape" + input: "ParseSingleExample/key_image/class/text:output:0" + input: "ParseSingleExample/Reshape_1/shape:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "ParseSingleExample/key_image/encoded" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "" + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_2/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_2" + op: "Reshape" + input: "ParseSingleExample/key_image/encoded:output:0" + input: "ParseSingleExample/Reshape_2/shape:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "ParseSingleExample/key_image/format" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "jpeg" + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_3/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "ParseSingleExample/Reshape_3" + op: "Reshape" + input: "ParseSingleExample/key_image/format:output:0" + input: "ParseSingleExample/Reshape_3/shape:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "ParseSingleExample/ParseSingleExample" + op: "ParseSingleExample" + input: "arg0" + input: "ParseSingleExample/Reshape:output:0" + input: "ParseSingleExample/Reshape_1:output:0" + input: "ParseSingleExample/Reshape_2:output:0" + input: "ParseSingleExample/Reshape_3:output:0" + attr { + key: "Tdense" + value { + list { + type: DT_INT64 + type: DT_STRING + type: DT_STRING + type: DT_STRING + } + } + } + attr { + key: "dense_keys" + value { + list { + s: "image/class/label" + s: "image/class/text" + s: "image/encoded" + s: "image/format" + } + } + } + attr { + key: "dense_shapes" + value { + list { + shape { + } + shape { + } + shape { + } + shape { + } + } + } + } + attr { + key: "num_sparse" + value { + i: 5 + } + } + attr { + key: "sparse_keys" + value { + list { + s: "image/object/bbox/xmax" + s: "image/object/bbox/xmin" + s: "image/object/bbox/ymax" + s: "image/object/bbox/ymin" + s: "image/object/class/label" + } + } + } + attr { + key: "sparse_types" + value { + list { + type: DT_FLOAT + type: DT_FLOAT + type: DT_FLOAT + type: DT_FLOAT + type: DT_INT64 + } + } + } + } + node_def { + name: "Reshape/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "Reshape" + op: "Reshape" + input: "ParseSingleExample/ParseSingleExample:dense_values:2" + input: "Reshape/shape:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/Substr/pos" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } + } + node_def { + name: "decode_image/Substr/len" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/Substr" + op: "Substr" + input: "Reshape:output:0" + input: "decode_image/Substr/pos:output:0" + input: "decode_image/Substr/len:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/is_jpeg/Substr/pos" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } + } + node_def { + name: "decode_image/is_jpeg/Substr/len" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/is_jpeg/Substr" + op: "Substr" + input: "Reshape:output:0" + input: "decode_image/is_jpeg/Substr/pos:output:0" + input: "decode_image/is_jpeg/Substr/len:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/is_jpeg/Equal/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "\377\330\377" + } + } + } + } + node_def { + name: "decode_image/is_jpeg/Equal" + op: "Equal" + input: "decode_image/is_jpeg/Substr:output:0" + input: "decode_image/is_jpeg/Equal/y:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + } + node_def { + name: "decode_image/cond_jpeg/Switch" + op: "Switch" + input: "decode_image/is_jpeg/Equal:z:0" + input: "decode_image/is_jpeg/Equal:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/switch_t" + op: "Identity" + input: "decode_image/cond_jpeg/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/switch_f" + op: "Identity" + input: "decode_image/cond_jpeg/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/pred_id" + op: "Identity" + input: "decode_image/is_jpeg/Equal:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/check_jpeg_channels/x" + op: "Const" + input: "^decode_image/cond_jpeg/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/check_jpeg_channels/y" + op: "Const" + input: "^decode_image/cond_jpeg/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 4 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/check_jpeg_channels" + op: "NotEqual" + input: "decode_image/cond_jpeg/check_jpeg_channels/x:output:0" + input: "decode_image/cond_jpeg/check_jpeg_channels/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/cond_jpeg/Assert/Const" + op: "Const" + input: "^decode_image/cond_jpeg/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 1, 3) when decoding JPEG images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/Assert/Assert/data_0" + op: "Const" + input: "^decode_image/cond_jpeg/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 1, 3) when decoding JPEG images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/Assert/Assert" + op: "Assert" + input: "decode_image/cond_jpeg/check_jpeg_channels:z:0" + input: "decode_image/cond_jpeg/Assert/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "decode_image/cond_jpeg/DecodeJpeg" + op: "DecodeJpeg" + input: "decode_image/cond_jpeg/DecodeJpeg/Switch:output_true:0" + input: "^decode_image/cond_jpeg/Assert/Assert" + attr { + key: "acceptable_fraction" + value { + f: 1.0 + } + } + attr { + key: "channels" + value { + i: 3 + } + } + attr { + key: "dct_method" + value { + s: "" + } + } + attr { + key: "fancy_upscaling" + value { + b: true + } + } + attr { + key: "ratio" + value { + i: 1 + } + } + attr { + key: "try_recover_truncated" + value { + b: false + } + } + } + node_def { + name: "decode_image/cond_jpeg/DecodeJpeg/Switch" + op: "Switch" + input: "Reshape:output:0" + input: "decode_image/cond_jpeg/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/is_png/y" + op: "Const" + input: "^decode_image/cond_jpeg/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "\211PN" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/is_png" + op: "Equal" + input: "decode_image/cond_jpeg/is_png/Switch:output_false:0" + input: "decode_image/cond_jpeg/is_png/y:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + } + node_def { + name: "decode_image/cond_jpeg/is_png/Switch" + op: "Switch" + input: "decode_image/Substr:output:0" + input: "decode_image/cond_jpeg/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@decode_image/Substr" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/Switch" + op: "Switch" + input: "decode_image/cond_jpeg/is_png:z:0" + input: "decode_image/cond_jpeg/is_png:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/switch_t" + op: "Identity" + input: "decode_image/cond_jpeg/cond_png/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/switch_f" + op: "Identity" + input: "decode_image/cond_jpeg/cond_png/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/pred_id" + op: "Identity" + input: "decode_image/cond_jpeg/is_png:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/DecodePng" + op: "DecodePng" + input: "decode_image/cond_jpeg/cond_png/DecodePng/Switch_1:output_true:0" + attr { + key: "channels" + value { + i: 3 + } + } + attr { + key: "dtype" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/DecodePng/Switch" + op: "Switch" + input: "Reshape:output:0" + input: "decode_image/cond_jpeg/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/DecodePng/Switch_1" + op: "Switch" + input: "decode_image/cond_jpeg/cond_png/DecodePng/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/is_gif/y" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "GIF" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/is_gif" + op: "Equal" + input: "decode_image/cond_jpeg/cond_png/is_gif/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/is_gif/y:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/is_gif/Switch" + op: "Switch" + input: "decode_image/cond_jpeg/is_png/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@decode_image/Substr" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Switch" + op: "Switch" + input: "decode_image/cond_jpeg/cond_png/is_gif:z:0" + input: "decode_image/cond_jpeg/cond_png/is_gif:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + op: "Identity" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + op: "Identity" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/pred_id" + op: "Identity" + input: "decode_image/cond_jpeg/cond_png/is_gif:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels/x" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels/y" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels" + op: "NotEqual" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels/x:output:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1/x" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1/y" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 4 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1" + op: "NotEqual" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1/x:output:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/LogicalAnd" + op: "LogicalAnd" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels:z:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_gif_channels_1:z:0" + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert/Const" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 3) when decoding GIF images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert/Assert/data_0" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 3) when decoding GIF images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert/Assert" + op: "Assert" + input: "decode_image/cond_jpeg/cond_png/cond_gif/LogicalAnd:z:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Assert/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif" + op: "DecodeGif" + input: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif/Switch_1:output_true:0" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/Assert/Assert" + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif/Switch" + op: "Switch" + input: "decode_image/cond_jpeg/cond_png/DecodePng/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif/Switch_1" + op: "Switch" + input: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/pos" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 0 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/len" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 2 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Substr" + op: "Substr" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/pos:output:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/len:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/Switch" + op: "Switch" + input: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif/Switch:output_false:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/pred_id:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "_class" + value { + list { + s: "loc:@Reshape" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/is_bmp/y" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "BM" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/is_bmp" + op: "Equal" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Substr:output:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/is_bmp/y:output:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_1/Const" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Unable to decode bytes as JPEG, PNG, GIF, or BMP" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_1/Assert/data_0" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Unable to decode bytes as JPEG, PNG, GIF, or BMP" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_1/Assert" + op: "Assert" + input: "decode_image/cond_jpeg/cond_png/cond_gif/is_bmp:z:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_1/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels/x" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels/y" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels" + op: "NotEqual" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels/x:output:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_2/Const" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 3) when decoding BMP images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_2/Assert/data_0" + op: "Const" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/switch_f" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Channels must be in (None, 0, 3) when decoding BMP images" + } + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_2/Assert" + op: "Assert" + input: "decode_image/cond_jpeg/cond_png/cond_gif/check_channels:z:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Assert_2/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeBmp" + op: "DecodeBmp" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Substr/Switch:output_false:0" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/Assert_1/Assert" + input: "^decode_image/cond_jpeg/cond_png/cond_gif/Assert_2/Assert" + attr { + key: "channels" + value { + i: 0 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/cond_gif/Merge" + op: "Merge" + input: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeBmp:image:0" + input: "decode_image/cond_jpeg/cond_png/cond_gif/DecodeGif:image:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "decode_image/cond_jpeg/cond_png/Merge" + op: "Merge" + input: "decode_image/cond_jpeg/cond_png/cond_gif/Merge:output:0" + input: "decode_image/cond_jpeg/cond_png/DecodePng:image:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "decode_image/cond_jpeg/Merge" + op: "Merge" + input: "decode_image/cond_jpeg/cond_png/Merge:output:0" + input: "decode_image/cond_jpeg/DecodeJpeg:image:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "convert_image/Cast" + op: "Cast" + input: "decode_image/cond_jpeg/Merge:output:0" + attr { + key: "DstT" + value { + type: DT_FLOAT + } + } + attr { + key: "SrcT" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "convert_image/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.00392156885937 + } + } + } + } + node_def { + name: "convert_image" + op: "Mul" + input: "convert_image/Cast:y:0" + input: "convert_image/y:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "Const" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 1 + } + dim { + size: 1 + } + dim { + size: 4 + } + } + tensor_content: "\000\000\000\000\000\000\000\000\000\000\200?\000\000\200?" + } + } + } + } + node_def { + name: "distorted_bounding_box_crop/Shape" + op: "Shape" + input: "convert_image:z:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "distorted_bounding_box_crop/sample_distorted_bounding_box/SampleDistortedBoundingBoxV2/min_object_covered" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.10000000149 + } + } + } + } + node_def { + name: "distorted_bounding_box_crop/sample_distorted_bounding_box/SampleDistortedBoundingBoxV2" + op: "SampleDistortedBoundingBoxV2" + input: "distorted_bounding_box_crop/Shape:output:0" + input: "Const:output:0" + input: "distorted_bounding_box_crop/sample_distorted_bounding_box/SampleDistortedBoundingBoxV2/min_object_covered:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "area_range" + value { + list { + f: 0.0799999982119 + f: 1.0 + } + } + } + attr { + key: "aspect_ratio_range" + value { + list { + f: 0.75 + f: 1.33333337307 + } + } + } + attr { + key: "max_attempts" + value { + i: 1 + } + } + attr { + key: "seed" + value { + i: 0 + } + } + attr { + key: "seed2" + value { + i: 0 + } + } + attr { + key: "use_image_if_no_bounding_boxes" + value { + b: true + } + } + } + node_def { + name: "distorted_bounding_box_crop/Slice" + op: "Slice" + input: "convert_image:z:0" + input: "distorted_bounding_box_crop/sample_distorted_bounding_box/SampleDistortedBoundingBoxV2:begin:0" + input: "distorted_bounding_box_crop/sample_distorted_bounding_box/SampleDistortedBoundingBoxV2:size:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "Shape" + op: "Shape" + input: "convert_image:z:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "Shape_1" + op: "Shape" + input: "distorted_bounding_box_crop/Slice:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "Equal" + op: "Equal" + input: "Shape:output:0" + input: "Shape_1:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "Cast" + op: "Cast" + input: "Equal:z:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_BOOL + } + } + } + node_def { + name: "Const_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "Sum" + op: "Sum" + input: "Cast:y:0" + input: "Const_1:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + attr { + key: "keep_dims" + value { + b: false + } + } + } + node_def { + name: "GreaterEqual/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "GreaterEqual" + op: "GreaterEqual" + input: "Sum:output:0" + input: "GreaterEqual/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/Switch" + op: "Switch" + input: "GreaterEqual:z:0" + input: "GreaterEqual:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/switch_t" + op: "Identity" + input: "cond/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/switch_f" + op: "Identity" + input: "cond/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/pred_id" + op: "Identity" + input: "GreaterEqual:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/Shape" + op: "Shape" + input: "cond/Shape/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/Shape/Switch" + op: "Switch" + input: "convert_image:z:0" + input: "cond/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@convert_image" + } + } + } + } + node_def { + name: "cond/Cast" + op: "Cast" + input: "cond/Shape:output:0" + attr { + key: "DstT" + value { + type: DT_FLOAT + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/strided_slice/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/strided_slice/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice" + op: "StridedSlice" + input: "cond/Cast:y:0" + input: "cond/strided_slice/stack:output:0" + input: "cond/strided_slice/stack_1:output:0" + input: "cond/strided_slice/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/strided_slice_1/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_1/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/strided_slice_1/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_1" + op: "StridedSlice" + input: "cond/Cast:y:0" + input: "cond/strided_slice_1/stack:output:0" + input: "cond/strided_slice_1/stack_1:output:0" + input: "cond/strided_slice_1/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/Greater" + op: "Greater" + input: "cond/strided_slice:output:0" + input: "cond/strided_slice_1:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/Switch" + op: "Switch" + input: "cond/Greater:z:0" + input: "cond/Greater:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/cond/switch_t" + op: "Identity" + input: "cond/cond/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/cond/switch_f" + op: "Identity" + input: "cond/cond/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/cond/pred_id" + op: "Identity" + input: "cond/Greater:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "cond/cond/strided_slice/stack" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/cond/strided_slice/stack_1" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice/stack_2" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice" + op: "StridedSlice" + input: "cond/cond/strided_slice/Switch:output_true:0" + input: "cond/cond/strided_slice/stack:output:0" + input: "cond/cond/strided_slice/stack_1:output:0" + input: "cond/cond/strided_slice/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/cond/strided_slice/Switch" + op: "Switch" + input: "cond/Cast:y:0" + input: "cond/cond/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@cond/Cast" + } + } + } + } + node_def { + name: "cond/cond/strided_slice_1/stack" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_1/stack_1" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_1/stack_2" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_1" + op: "StridedSlice" + input: "cond/cond/strided_slice/Switch:output_true:0" + input: "cond/cond/strided_slice_1/stack:output:0" + input: "cond/cond/strided_slice_1/stack_1:output:0" + input: "cond/cond/strided_slice_1/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/cond/truediv" + op: "RealDiv" + input: "cond/cond/strided_slice:output:0" + input: "cond/cond/strided_slice_1:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/mul/y" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 224.0 + } + } + } + } + node_def { + name: "cond/cond/mul" + op: "Mul" + input: "cond/cond/truediv:z:0" + input: "cond/cond/mul/y:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/Cast/x/1" + op: "Const" + input: "^cond/cond/switch_t" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 224.0 + } + } + } + } + node_def { + name: "cond/cond/Cast/x" + op: "Pack" + input: "cond/cond/mul:z:0" + input: "cond/cond/Cast/x/1:output:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/cond/Cast" + op: "Cast" + input: "cond/cond/Cast/x:output:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/strided_slice_2/stack" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_2/stack_1" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_2/stack_2" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_2" + op: "StridedSlice" + input: "cond/cond/strided_slice_2/Switch:output_false:0" + input: "cond/cond/strided_slice_2/stack:output:0" + input: "cond/cond/strided_slice_2/stack_1:output:0" + input: "cond/cond/strided_slice_2/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/cond/strided_slice_2/Switch" + op: "Switch" + input: "cond/Cast:y:0" + input: "cond/cond/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@cond/Cast" + } + } + } + } + node_def { + name: "cond/cond/strided_slice_3/stack" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_3/stack_1" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_3/stack_2" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/cond/strided_slice_3" + op: "StridedSlice" + input: "cond/cond/strided_slice_2/Switch:output_false:0" + input: "cond/cond/strided_slice_3/stack:output:0" + input: "cond/cond/strided_slice_3/stack_1:output:0" + input: "cond/cond/strided_slice_3/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/cond/truediv_1" + op: "RealDiv" + input: "cond/cond/strided_slice_2:output:0" + input: "cond/cond/strided_slice_3:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/mul_1/y" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 224.0 + } + } + } + } + node_def { + name: "cond/cond/mul_1" + op: "Mul" + input: "cond/cond/truediv_1:z:0" + input: "cond/cond/mul_1/y:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/Cast_1/x/0" + op: "Const" + input: "^cond/cond/switch_f" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 224.0 + } + } + } + } + node_def { + name: "cond/cond/Cast_1/x" + op: "Pack" + input: "cond/cond/Cast_1/x/0:output:0" + input: "cond/cond/mul_1:z:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/cond/Cast_1" + op: "Cast" + input: "cond/cond/Cast_1/x:output:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/cond/Merge" + op: "Merge" + input: "cond/cond/Cast_1:y:0" + input: "cond/cond/Cast:y:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/ResizeBicubic/images" + op: "Pack" + input: "cond/Shape/Switch:output_true:0" + attr { + key: "N" + value { + i: 1 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/ResizeBicubic" + op: "ResizeBicubic" + input: "cond/ResizeBicubic/images:output:0" + input: "cond/cond/Merge:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "align_corners" + value { + b: false + } + } + } + node_def { + name: "cond/strided_slice_2/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/strided_slice_2/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_2/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_2" + op: "StridedSlice" + input: "cond/ResizeBicubic:resized_images:0" + input: "cond/strided_slice_2/stack:output:0" + input: "cond/strided_slice_2/stack_1:output:0" + input: "cond/strided_slice_2/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/Shape_1" + op: "Shape" + input: "cond/strided_slice_2:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/strided_slice_3/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/strided_slice_3/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_3/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_3" + op: "StridedSlice" + input: "cond/Shape_1:output:0" + input: "cond/strided_slice_3/stack:output:0" + input: "cond/strided_slice_3/stack_1:output:0" + input: "cond/strided_slice_3/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/Shape_2" + op: "Shape" + input: "cond/strided_slice_2:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/strided_slice_4/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_4/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/strided_slice_4/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_4" + op: "StridedSlice" + input: "cond/Shape_2:output:0" + input: "cond/strided_slice_4/stack:output:0" + input: "cond/strided_slice_4/stack_1:output:0" + input: "cond/strided_slice_4/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/sub/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/sub" + op: "Sub" + input: "cond/strided_slice_3:output:0" + input: "cond/sub/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/add/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/add" + op: "Add" + input: "cond/sub:z:0" + input: "cond/add/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/truediv/Cast" + op: "Cast" + input: "cond/add:z:0" + attr { + key: "DstT" + value { + type: DT_DOUBLE + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv/Cast_1" + op: "Cast" + input: "cond/truediv/y:output:0" + attr { + key: "DstT" + value { + type: DT_DOUBLE + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv" + op: "RealDiv" + input: "cond/truediv/Cast:y:0" + input: "cond/truediv/Cast_1:y:0" + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + } + node_def { + name: "cond/sub_1/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/sub_1" + op: "Sub" + input: "cond/strided_slice_4:output:0" + input: "cond/sub_1/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/add_1/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/add_1" + op: "Add" + input: "cond/sub_1:z:0" + input: "cond/add_1/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv_1/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/truediv_1/Cast" + op: "Cast" + input: "cond/add_1:z:0" + attr { + key: "DstT" + value { + type: DT_DOUBLE + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv_1/Cast_1" + op: "Cast" + input: "cond/truediv_1/y:output:0" + attr { + key: "DstT" + value { + type: DT_DOUBLE + } + } + attr { + key: "SrcT" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/truediv_1" + op: "RealDiv" + input: "cond/truediv_1/Cast:y:0" + input: "cond/truediv_1/Cast_1:y:0" + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + } + node_def { + name: "cond/Shape_3" + op: "Shape" + input: "cond/strided_slice_2:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/Rank" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "cond/Equal/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 3 + } + } + } + } + node_def { + name: "cond/Equal" + op: "Equal" + input: "cond/Rank:output:0" + input: "cond/Equal/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/Assert/Const" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Rank of image must be equal to 3." + } + } + } + } + node_def { + name: "cond/Assert/Assert/data_0" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Rank of image must be equal to 3." + } + } + } + } + node_def { + name: "cond/Assert/Assert" + op: "Assert" + input: "cond/Equal:z:0" + input: "cond/Assert/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "cond/strided_slice_5/stack" + op: "Const" + input: "^cond/Assert/Assert" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/strided_slice_5/stack_1" + op: "Const" + input: "^cond/Assert/Assert" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 3 + } + } + } + } + node_def { + name: "cond/strided_slice_5/stack_2" + op: "Const" + input: "^cond/Assert/Assert" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_5" + op: "StridedSlice" + input: "cond/Shape_3:output:0" + input: "cond/strided_slice_5/stack:output:0" + input: "cond/strided_slice_5/stack_1:output:0" + input: "cond/strided_slice_5/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/stack/0" + op: "Const" + input: "^cond/Assert/Assert" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/stack/1" + op: "Const" + input: "^cond/Assert/Assert" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/stack" + op: "Pack" + input: "cond/stack/0:output:0" + input: "cond/stack/1:output:0" + input: "cond/strided_slice_5:output:0" + attr { + key: "N" + value { + i: 3 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/strided_slice_6/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/strided_slice_6/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_6/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_6" + op: "StridedSlice" + input: "cond/Shape_3:output:0" + input: "cond/strided_slice_6/stack:output:0" + input: "cond/strided_slice_6/stack_1:output:0" + input: "cond/strided_slice_6/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/GreaterEqual/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/GreaterEqual" + op: "GreaterEqual" + input: "cond/strided_slice_6:output:0" + input: "cond/GreaterEqual/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/strided_slice_7/stack" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_7/stack_1" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 2 + } + } + } + } + node_def { + name: "cond/strided_slice_7/stack_2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_7" + op: "StridedSlice" + input: "cond/Shape_3:output:0" + input: "cond/strided_slice_7/stack:output:0" + input: "cond/strided_slice_7/stack_1:output:0" + input: "cond/strided_slice_7/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/GreaterEqual_1/y" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 224 + } + } + } + } + node_def { + name: "cond/GreaterEqual_1" + op: "GreaterEqual" + input: "cond/strided_slice_7:output:0" + input: "cond/GreaterEqual_1/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/LogicalAnd" + op: "LogicalAnd" + input: "cond/GreaterEqual:z:0" + input: "cond/GreaterEqual_1:z:0" + } + node_def { + name: "cond/Assert_1/Const" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Crop size greater than the image size." + } + } + } + } + node_def { + name: "cond/Assert_1/Assert/data_0" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "Crop size greater than the image size." + } + } + } + } + node_def { + name: "cond/Assert_1/Assert" + op: "Assert" + input: "cond/LogicalAnd:z:0" + input: "cond/Assert_1/Assert/data_0:output:0" + attr { + key: "T" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "summarize" + value { + i: 3 + } + } + } + node_def { + name: "cond/stack_1/2" + op: "Const" + input: "^cond/switch_t" + attr { + key: "dtype" + value { + type: DT_DOUBLE + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_DOUBLE + tensor_shape { + } + double_val: 0.0 + } + } + } + } + node_def { + name: "cond/stack_1" + op: "Pack" + input: "cond/truediv:z:0" + input: "cond/truediv_1:z:0" + input: "cond/stack_1/2:output:0" + attr { + key: "N" + value { + i: 3 + } + } + attr { + key: "T" + value { + type: DT_DOUBLE + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/ToInt32" + op: "Cast" + input: "cond/stack_1:output:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_DOUBLE + } + } + } + node_def { + name: "cond/Slice" + op: "Slice" + input: "cond/strided_slice_2:output:0" + input: "cond/ToInt32:y:0" + input: "cond/stack:output:0" + input: "^cond/Assert_1/Assert" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "cond/Reshape" + op: "Reshape" + input: "cond/Slice:output:0" + input: "cond/stack:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "cond/ResizeBicubic_1/images" + op: "Pack" + input: "cond/ResizeBicubic_1/images/Switch:output_false:0" + attr { + key: "N" + value { + i: 1 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "axis" + value { + i: 0 + } + } + } + node_def { + name: "cond/ResizeBicubic_1/images/Switch" + op: "Switch" + input: "distorted_bounding_box_crop/Slice:output:0" + input: "cond/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@distorted_bounding_box_crop/Slice" + } + } + } + } + node_def { + name: "cond/ResizeBicubic_1/size" + op: "Const" + input: "^cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 2 + } + } + tensor_content: "\340\000\000\000\340\000\000\000" + } + } + } + } + node_def { + name: "cond/ResizeBicubic_1" + op: "ResizeBicubic" + input: "cond/ResizeBicubic_1/images:output:0" + input: "cond/ResizeBicubic_1/size:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "align_corners" + value { + b: false + } + } + } + node_def { + name: "cond/strided_slice_8/stack" + op: "Const" + input: "^cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "cond/strided_slice_8/stack_1" + op: "Const" + input: "^cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_8/stack_2" + op: "Const" + input: "^cond/switch_f" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "cond/strided_slice_8" + op: "StridedSlice" + input: "cond/ResizeBicubic_1:resized_images:0" + input: "cond/strided_slice_8/stack:output:0" + input: "cond/strided_slice_8/stack_1:output:0" + input: "cond/strided_slice_8/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "cond/Merge" + op: "Merge" + input: "cond/strided_slice_8:output:0" + input: "cond/Reshape:output:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "Const_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 1 + } + dim { + size: 1 + } + dim { + size: 3 + } + } + tensor_content: "\354Q\370>\325x\351>;\337\317>" + } + } + } + } + node_def { + name: "sub" + op: "Sub" + input: "cond/Merge:output:0" + input: "Const_2:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "Const_3" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { + size: 1 + } + dim { + size: 1 + } + dim { + size: 3 + } + } + tensor_content: "\372~j>B`e>fff>" + } + } + } + } + node_def { + name: "truediv" + op: "RealDiv" + input: "sub:z:0" + input: "Const_3:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "random_flip_left_right/control_dependency" + op: "Identity" + input: "truediv:z:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@truediv" + } + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/min" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.0 + } + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/max" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 1.0 + } + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/RandomUniform" + op: "RandomUniform" + input: "random_flip_left_right/random_uniform/shape:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "seed" + value { + i: 0 + } + } + attr { + key: "seed2" + value { + i: 0 + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/sub" + op: "Sub" + input: "random_flip_left_right/random_uniform/max:output:0" + input: "random_flip_left_right/random_uniform/min:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "random_flip_left_right/random_uniform/mul" + op: "Mul" + input: "random_flip_left_right/random_uniform/RandomUniform:output:0" + input: "random_flip_left_right/random_uniform/sub:z:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "random_flip_left_right/random_uniform" + op: "Add" + input: "random_flip_left_right/random_uniform/mul:z:0" + input: "random_flip_left_right/random_uniform/min:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "random_flip_left_right/Less/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 0.5 + } + } + } + } + node_def { + name: "random_flip_left_right/Less" + op: "Less" + input: "random_flip_left_right/random_uniform:z:0" + input: "random_flip_left_right/Less/y:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "random_flip_left_right/Switch" + op: "Switch" + input: "random_flip_left_right/Less:z:0" + input: "random_flip_left_right/Less:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "random_flip_left_right/switch_t" + op: "Identity" + input: "random_flip_left_right/Switch:output_true:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "random_flip_left_right/switch_f" + op: "Identity" + input: "random_flip_left_right/Switch:output_false:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "random_flip_left_right/pred_id" + op: "Identity" + input: "random_flip_left_right/Less:z:0" + attr { + key: "T" + value { + type: DT_BOOL + } + } + } + node_def { + name: "random_flip_left_right/ReverseV2/axis" + op: "Const" + input: "^random_flip_left_right/switch_t" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "random_flip_left_right/ReverseV2" + op: "ReverseV2" + input: "random_flip_left_right/ReverseV2/Switch:output_true:0" + input: "random_flip_left_right/ReverseV2/axis:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tidx" + value { + type: DT_INT32 + } + } + } + node_def { + name: "random_flip_left_right/ReverseV2/Switch" + op: "Switch" + input: "random_flip_left_right/control_dependency:output:0" + input: "random_flip_left_right/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@truediv" + } + } + } + } + node_def { + name: "random_flip_left_right/Switch_1" + op: "Switch" + input: "random_flip_left_right/control_dependency:output:0" + input: "random_flip_left_right/pred_id:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "_class" + value { + list { + s: "loc:@truediv" + } + } + } + } + node_def { + name: "random_flip_left_right/Merge" + op: "Merge" + input: "random_flip_left_right/Switch_1:output_false:0" + input: "random_flip_left_right/ReverseV2:output:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + node_def { + name: "Reshape_1/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 3 + } + } + tensor_content: "\340\000\000\000\340\000\000\000\003\000\000\000" + } + } + } + } + node_def { + name: "Reshape_1" + op: "Reshape" + input: "random_flip_left_right/Merge:output:0" + input: "Reshape_1/shape:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "Reshape_2/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "Reshape_2" + op: "Reshape" + input: "ParseSingleExample/ParseSingleExample:dense_values:0" + input: "Reshape_2/shape:output:0" + attr { + key: "T" + value { + type: DT_INT64 + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "Cast_1" + op: "Cast" + input: "Reshape_2:output:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_INT64 + } + } + } + node_def { + name: "sub_1/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 1 + } + } + } + } + node_def { + name: "sub_1" + op: "Sub" + input: "Cast_1:y:0" + input: "sub_1/y:output:0" + attr { + key: "T" + value { + type: DT_INT32 + } + } + } + ret { + key: "Reshape_1" + value: "Reshape_1:output:0" + } + ret { + key: "sub_1" + value: "sub_1:z:0" + } + } + function { + signature { + name: "tf_predicate_7089b845" + input_arg { + name: "arg0" + type: DT_FLOAT + } + input_arg { + name: "arg1" + type: DT_INT32 + } + input_arg { + name: "Equal/Placeholder" + type: DT_INT64 + } + output_arg { + name: "Equal" + type: DT_BOOL + } + description: "A wrapper for Defun that facilitates shape inference." + } + node_def { + name: "Shape" + op: "Shape" + input: "arg0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT64 + } + } + } + node_def { + name: "strided_slice/stack" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "strided_slice/stack_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "strided_slice/stack_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "strided_slice" + op: "StridedSlice" + input: "Shape:output:0" + input: "strided_slice/stack:output:0" + input: "strided_slice/stack_1:output:0" + input: "strided_slice/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT64 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "Equal" + op: "Equal" + input: "strided_slice:output:0" + input: "Equal/Placeholder" + attr { + key: "T" + value { + type: DT_INT64 + } + } + } + ret { + key: "Equal" + value: "Equal:z:0" + } + } + function { + signature { + name: "_make_dataset_5fa5e1f4" + output_arg { + name: "PrefetchDataset_1" + type: DT_VARIANT + } + is_stateful: true + } + node_def { + name: "TensorSliceDataset/MatchingFiles/pattern" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "$(DATA_DIR)" + } + } + } + } + node_def { + name: "TensorSliceDataset/MatchingFiles" + op: "MatchingFiles" + input: "TensorSliceDataset/MatchingFiles/pattern:output:0" + } + node_def { + name: "TensorSliceDataset" + op: "TensorSliceDataset" + input: "TensorSliceDataset/MatchingFiles:filenames:0" + attr { + key: "Toutput_types" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + } + node_def { + name: "ShuffleDataset/MatchingFiles/pattern" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "$(DATA_DIR)" + } + } + } + } + node_def { + name: "ShuffleDataset/MatchingFiles" + op: "MatchingFiles" + input: "ShuffleDataset/MatchingFiles/pattern:output:0" + } + node_def { + name: "ShuffleDataset/Shape" + op: "Shape" + input: "ShuffleDataset/MatchingFiles:filenames:0" + attr { + key: "T" + value { + type: DT_STRING + } + } + attr { + key: "out_type" + value { + type: DT_INT64 + } + } + } + node_def { + name: "ShuffleDataset/strided_slice/stack" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset/strided_slice/stack_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "ShuffleDataset/strided_slice/stack_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "ShuffleDataset/strided_slice" + op: "StridedSlice" + input: "ShuffleDataset/Shape:output:0" + input: "ShuffleDataset/strided_slice/stack:output:0" + input: "ShuffleDataset/strided_slice/stack_1:output:0" + input: "ShuffleDataset/strided_slice/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT64 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "ShuffleDataset/Maximum/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 1 + } + } + } + } + node_def { + name: "ShuffleDataset/Maximum" + op: "Maximum" + input: "ShuffleDataset/strided_slice:output:0" + input: "ShuffleDataset/Maximum/y:output:0" + attr { + key: "T" + value { + type: DT_INT64 + } + } + } + node_def { + name: "ShuffleDataset/seed" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset/seed2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset" + op: "ShuffleDataset" + input: "TensorSliceDataset:handle:0" + input: "ShuffleDataset/Maximum:z:0" + input: "ShuffleDataset/seed:output:0" + input: "ShuffleDataset/seed2:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "reshuffle_each_iteration" + value { + b: true + } + } + } + node_def { + name: "ShuffleDataset_1/buffer_size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 1024 + } + } + } + } + node_def { + name: "ShuffleDataset_1/seed_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset_1/seed2_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset_1" + op: "ShuffleDataset" + input: "ShuffleDataset:handle:0" + input: "ShuffleDataset_1/buffer_size:output:0" + input: "ShuffleDataset_1/seed_1:output:0" + input: "ShuffleDataset_1/seed2_1:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "reshuffle_each_iteration" + value { + b: true + } + } + } + node_def { + name: "RepeatDataset/count" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: -1 + } + } + } + } + node_def { + name: "RepeatDataset" + op: "RepeatDataset" + input: "ShuffleDataset_1:handle:0" + input: "RepeatDataset/count:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + } + node_def { + name: "ParallelInterleaveDataset/cycle_length" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 8 + } + } + } + } + node_def { + name: "ParallelInterleaveDataset/block_length" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 1 + } + } + } + } + node_def { + name: "ParallelInterleaveDataset/sloppy" + op: "Const" + attr { + key: "dtype" + value { + type: DT_BOOL + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_BOOL + tensor_shape { + } + bool_val: true + } + } + } + } + node_def { + name: "ParallelInterleaveDataset/buffer_output_elements" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 2 + } + } + } + } + node_def { + name: "ParallelInterleaveDataset/prefetch_input_elements" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 16 + } + } + } + } + node_def { + name: "ParallelInterleaveDataset" + op: "ParallelInterleaveDataset" + input: "RepeatDataset:handle:0" + input: "ParallelInterleaveDataset/cycle_length:output:0" + input: "ParallelInterleaveDataset/block_length:output:0" + input: "ParallelInterleaveDataset/sloppy:output:0" + input: "ParallelInterleaveDataset/buffer_output_elements:output:0" + input: "ParallelInterleaveDataset/prefetch_input_elements:output:0" + attr { + key: "Targuments" + value { + list { + } + } + } + attr { + key: "f" + value { + func { + name: "tf_map_func_91295dea" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + } + node_def { + name: "ShuffleDataset_2/buffer_size_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 1024 + } + } + } + } + node_def { + name: "ShuffleDataset_2/seed_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset_2/seed2_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset_2" + op: "ShuffleDataset" + input: "ParallelInterleaveDataset:handle:0" + input: "ShuffleDataset_2/buffer_size_1:output:0" + input: "ShuffleDataset_2/seed_2:output:0" + input: "ShuffleDataset_2/seed2_2:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_STRING + } + } + } + attr { + key: "reshuffle_each_iteration" + value { + b: true + } + } + } + node_def { + name: "ParallelMapDataset/num_parallel_calls" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + } + int_val: 64 + } + } + } + } + node_def { + name: "ParallelMapDataset" + op: "ParallelMapDataset" + input: "ShuffleDataset_2:handle:0" + input: "ParallelMapDataset/num_parallel_calls:output:0" + attr { + key: "Targuments" + value { + list { + } + } + } + attr { + key: "f" + value { + func { + name: "tf_map_func_74b6b15c" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "PrefetchDataset/buffer_size_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 64 + } + } + } + } + node_def { + name: "PrefetchDataset" + op: "PrefetchDataset" + input: "ParallelMapDataset:handle:0" + input: "PrefetchDataset/buffer_size_2:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "BatchDataset/batch_size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 64 + } + } + } + } + node_def { + name: "BatchDataset" + op: "BatchDataset" + input: "PrefetchDataset:handle:0" + input: "BatchDataset/batch_size:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "FilterDataset/batch_size_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 64 + } + } + } + } + node_def { + name: "FilterDataset" + op: "FilterDataset" + input: "BatchDataset:handle:0" + input: "FilterDataset/batch_size_1:output:0" + attr { + key: "Targuments" + value { + list { + type: DT_INT64 + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + attr { + key: "predicate" + value { + func { + name: "tf_predicate_7089b845" + } + } + } + } + node_def { + name: "PrefetchDataset_1/buffer_size_3" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 2 + } + } + } + } + node_def { + name: "PrefetchDataset_1" + op: "PrefetchDataset" + input: "FilterDataset:handle:0" + input: "PrefetchDataset_1/buffer_size_3:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 64 + } + dim { + size: 224 + } + dim { + size: 224 + } + dim { + size: 3 + } + } + shape { + dim { + size: 64 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + ret { + key: "PrefetchDataset_1" + value: "PrefetchDataset_1:handle:0" + } + } +} +)PREFIX"; + + *dataset_name = "_make_dataset_5fa5e1f4"; + std::function mutate_proto_func = + [dataset_name, file_path](FunctionDef* fdef) { + VLOG(1) << "Processsing function " << fdef->DebugString(); + if (std::string(fdef->signature().name()) != *dataset_name) return; + // Change the input file pattern to `file_path`. + bool found = false; + for (auto& node_def : *fdef->mutable_node_def()) { + if (node_def.name() != "TensorSliceDataset/MatchingFiles/pattern" && + node_def.name() != "ShuffleDataset/MatchingFiles/pattern") + continue; + DCHECK_EQ(node_def.op(), "Const"); + DCHECK_GT(node_def.attr().count("value"), 0); + found = true; + DCHECK_EQ(node_def.attr().at("value").tensor().string_val(0), + "$(DATA_DIR)"); + VLOG(1) << "Setting the value of node_def " + "TensorSliceDataset/MatchingFiles/pattern to " + << file_path; + auto* tensor = (*node_def.mutable_attr())["value"].mutable_tensor(); + tensor->clear_string_val(); + tensor->add_string_val(file_path); + } + VLOG(1) << "Rewrote function to " << fdef->DebugString(); + DCHECK(found); + }; + return CreateFunctionsFromTextProto(func_def, &mutate_proto_func, status); +} + +// Adds the input functions to `graph`. On success, returns the created +// IteratorGetNext node. +static TF_Operation* AddDatasetFunctionAndIteratorNodesToGraph( + const std::vector& funcs, const std::string& dataset_name, + const std::vector& output_types, + const std::vector& output_shapes, + TF_Graph* graph, TF_Status* status) { + DCHECK(!dataset_name.empty()); + for (auto& func : funcs) { + TF_GraphCopyFunction(graph, func.get(), /*gradient*/ nullptr, status); + if (!status->status.ok()) { + return nullptr; + } + } + + tensorflow::mutex_lock c(graph->mu); + + tensorflow::NameAttrList func; + func.set_name(dataset_name); + // Run the iterator node on CPU. + Node* oneshot_iterator_node; + tensorflow::Status s = NodeBuilder("OneShotIterator", "OneShotIterator") + .Device("/device:CPU:0") + .Attr("container", "") + .Attr("dataset_factory", func) + .Attr("output_types", output_types) + .Attr("output_shapes", output_shapes) + .Attr("shared_name", "") + .Finalize(&graph->graph, &oneshot_iterator_node); + if (!s.ok()) { + status->status = s; + return nullptr; + } + // Run shape inference function for each newly added node, so that more + // subsequent nodes can be added to the graph via C API (TF_NewOperation()). + s = graph->refiner.AddNode(oneshot_iterator_node); + if (!s.ok()) { + status->status = s; + return nullptr; + } + + // Run the iterator node on CPU. + Node* getnext_node; + s = NodeBuilder("IteratorGetNext", "IteratorGetNext") + .Input(oneshot_iterator_node) + .Device("/device:CPU:0") + .Attr("output_types", output_types) + .Attr("output_shapes", output_shapes) + .Finalize(&graph->graph, &getnext_node); + if (!s.ok()) { + status->status = s; + return nullptr; + } + // Run shape inference function for each newly added node, so that more + // subsequent nodes can be added to the graph via C API (TF_NewOperation()). + s = graph->refiner.AddNode(getnext_node); + if (!s.ok()) { + status->status = s; + return nullptr; + } + + VLOG(1) << "Output graph: " << graph->graph.ToGraphDefDebug().DebugString(); + return ToTF_Operation(getnext_node); +} + +TF_Operation* TF_MakeFakeIteratorGetNextWithDatasets(TF_Graph* graph, + TF_Status* status) { + tensorflow::Status s; + + std::string dataset_name; + UniqueFuncPtr result_func = CreateFakeDatasetFunction(&dataset_name, status); + if (!status->status.ok()) { + return nullptr; + } + + std::vector funcs; + funcs.push_back(std::move(result_func)); + std::vector output_shape_list; + output_shape_list.push_back(tensorflow::TensorShapeProto()); + auto* getnext_node = AddDatasetFunctionAndIteratorNodesToGraph( + funcs, dataset_name, {tensorflow::DT_FLOAT}, output_shape_list, graph, + status); + if (!status->status.ok()) { + return nullptr; + } + + return getnext_node; +} + +TF_Operation* TF_MakeImagenetIteratorGetNextWithDatasets(TF_Graph* graph, + const char* file_path, + int batch_size, + TF_Status* status) { + tensorflow::Status s; + + std::string dataset_name; + const auto& funcs = + CreateImagenetDatasetFunctions(file_path, &dataset_name, status); + if (!status->status.ok()) { + return nullptr; + } + + std::vector output_shape_list; + // batch_size X 224 X 224 X 3 + auto image_shape = tensorflow::TensorShapeProto(); + image_shape.add_dim()->set_size(batch_size); + image_shape.add_dim()->set_size(224); + image_shape.add_dim()->set_size(224); + image_shape.add_dim()->set_size(3); + output_shape_list.push_back(image_shape); + + // batch_size + auto label_shape = tensorflow::TensorShapeProto(); + label_shape.add_dim()->set_size(batch_size); + output_shape_list.push_back(label_shape); + auto* getnext_node = AddDatasetFunctionAndIteratorNodesToGraph( + funcs, dataset_name, {tensorflow::DT_FLOAT, tensorflow::DT_INT32}, + output_shape_list, graph, status); + if (!status->status.ok()) { + return nullptr; + } + + tensorflow::mutex_lock c(graph->mu); + VLOG(1) << "The extended graph: " + << graph->graph.ToGraphDefDebug().DebugString(); - VLOG(1) << "Output graph: " << graph->graph.ToGraphDefDebug().DebugString(); - *dataset_func = result_func.release(); - return ToTF_Operation(getnext_node); -} - -void TF_GetAttrScalarTensorShapeProto(TF_Buffer* value, TF_Status* status) { - status->status = Status::OK(); - auto shape = tensorflow::TensorShape({}); - tensorflow::TensorShapeProto shape_proto; - shape.AsProto(&shape_proto); - status->status = MessageToBuffer(shape_proto, value); + return getnext_node; } diff --git a/tensorflow/c/c_api_experimental.h b/tensorflow/c/c_api_experimental.h index 2fa232878c..a9c551d73e 100644 --- a/tensorflow/c/c_api_experimental.h +++ b/tensorflow/c/c_api_experimental.h @@ -87,25 +87,22 @@ TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, TF_CAPI_EXPORT extern const char* TF_GraphDebugString(TF_Graph* graph, size_t* len); -// Creates a stack of data set + iterator nodes reading the TFRecord files from -// `file_path`, and outputs the following info on success: +// Creates a stack of data set + iterator nodes, currently hard-coded to return +// a sequence of 3 float values <42.0, 43.0, 44.0> over 3 calls. On success, +// returns the IteratorGetNext node, which caller can run or feed into an node. // -// 1. Returns the IteratorGetNext node, which caller can run or feed into an -// node. -// -// 2. Sets `dataset_func` to the created function that encapsulates the data set -// nodes. Caller owns that function, and must call TF_DeleteFunction() on it. -// -// -// The nodes are currently hard-coded to return a single Int32 of value 1. // TODO(hongm): Extend the API to allow customization of the nodes created. -TF_CAPI_EXPORT extern TF_Operation* TF_MakeIteratorGetNextWithDatasets( - TF_Graph* graph, const char* file_path, TF_Function** dataset_func, - TF_Status* status); - -// Returns the shape proto of shape {}. -TF_CAPI_EXPORT extern void TF_GetAttrScalarTensorShapeProto(TF_Buffer* value, - TF_Status* status); +TF_CAPI_EXPORT extern TF_Operation* TF_MakeFakeIteratorGetNextWithDatasets( + TF_Graph* graph, TF_Status* status); + +// Similar to the above API, except that the returned iterator reads the +// TFRecord files from `file_path`. +// The iterators outputs 2 tensors: +// - A float tensor of shape `batch_size` X 224 X 224 X 3 +// - An int32 tensor of shape `batch_size` +// TODO(hongm): Extend the API to allow customization of the nodes created. +TF_CAPI_EXPORT extern TF_Operation* TF_MakeImagenetIteratorGetNextWithDatasets( + TF_Graph* graph, const char* file_path, int batch_size, TF_Status* status); #ifdef __cplusplus } /* end extern "C" */ diff --git a/tensorflow/c/c_api_experimental_test.cc b/tensorflow/c/c_api_experimental_test.cc index 9ddd65f0c5..49d64d18bf 100644 --- a/tensorflow/c/c_api_experimental_test.cc +++ b/tensorflow/c/c_api_experimental_test.cc @@ -15,38 +15,36 @@ limitations under the License. #include "tensorflow/c/c_api_experimental.h" #include "tensorflow/c/c_test_util.h" +#include "tensorflow/core/lib/io/path.h" +#include "tensorflow/core/platform/logging.h" +#include "tensorflow/core/platform/test.h" namespace tensorflow { namespace { -void TestIteratorStack() { +void TestFakeIteratorStack() { TF_Status* s = TF_NewStatus(); TF_Graph* graph = TF_NewGraph(); - TF_Function* dataset_func = nullptr; - - TF_Operation* get_next = - TF_MakeIteratorGetNextWithDatasets(graph, "dummy_path", &dataset_func, s); + TF_Operation* get_next = TF_MakeFakeIteratorGetNextWithDatasets(graph, s); ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); - ASSERT_NE(dataset_func, nullptr); - TF_DeleteFunction(dataset_func); - CSession csession(graph, s); ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); // Run the graph. - for (int i = 0; i < 1; ++i) { + const float base_value = 42.0; + for (int i = 0; i < 3; ++i) { csession.SetOutputs({get_next}); csession.Run(s); ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); TF_Tensor* out = csession.output_tensor(0); ASSERT_TRUE(out != nullptr); - EXPECT_EQ(TF_INT32, TF_TensorType(out)); - EXPECT_EQ(0, TF_NumDims(out)); // scalar - ASSERT_EQ(sizeof(int32), TF_TensorByteSize(out)); - int32* output_contents = static_cast(TF_TensorData(out)); - EXPECT_EQ(1, *output_contents); + ASSERT_EQ(TF_FLOAT, TF_TensorType(out)); + ASSERT_EQ(0, TF_NumDims(out)); // scalar + ASSERT_EQ(sizeof(float), TF_TensorByteSize(out)); + float* output_contents = static_cast(TF_TensorData(out)); + ASSERT_EQ(base_value + i, *output_contents); } // This should error out since we've exhausted the iterator. @@ -60,7 +58,63 @@ void TestIteratorStack() { TF_DeleteStatus(s); } -TEST(CAPI_EXPERIMENTAL, IteratorGetNext) { TestIteratorStack(); } +TEST(CAPI_EXPERIMENTAL, FakeIteratorGetNext) { TestFakeIteratorStack(); } + +TEST(CAPI_EXPERIMENTAL, ImagenetIteratorGetNext) { + TF_Status* s = TF_NewStatus(); + TF_Graph* graph = TF_NewGraph(); + + const string file_path = tensorflow::io::JoinPath( + tensorflow::testing::TensorFlowSrcRoot(), "c/testdata/tf_record"); + VLOG(1) << "data file path is " << file_path; + const int batch_size = 64; + TF_Operation* get_next = TF_MakeImagenetIteratorGetNextWithDatasets( + graph, file_path.c_str(), batch_size, s); + ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); + + CSession csession(graph, s); + ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); + + // Run the graph. + // The two output tensors should look like: + // Tensor("IteratorGetNext:0", shape=(batch_size, 224, 224, 3), dtype=float32) + // Tensor("IteratorGetNext:1", shape=(batch_size, ), dtype=int32) + for (int i = 0; i < 3; ++i) { + LOG(INFO) << "Running iter " << i; + csession.SetOutputs({{get_next, 0}, {get_next, 1}}); + csession.Run(s); + ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); + + { + TF_Tensor* image = csession.output_tensor(0); + ASSERT_TRUE(image != nullptr); + ASSERT_EQ(TF_FLOAT, TF_TensorType(image)); + // Confirm shape is 224 X 224 X 3 + ASSERT_EQ(4, TF_NumDims(image)); + ASSERT_EQ(batch_size, TF_Dim(image, 0)); + ASSERT_EQ(224, TF_Dim(image, 1)); + ASSERT_EQ(224, TF_Dim(image, 2)); + ASSERT_EQ(3, TF_Dim(image, 3)); + ASSERT_EQ(sizeof(float) * batch_size * 224 * 224 * 3, + TF_TensorByteSize(image)); + } + + { + TF_Tensor* label = csession.output_tensor(1); + ASSERT_TRUE(label != nullptr); + ASSERT_EQ(TF_INT32, TF_TensorType(label)); + ASSERT_EQ(1, TF_NumDims(label)); + ASSERT_EQ(batch_size, TF_Dim(label, 0)); + ASSERT_EQ(sizeof(int32) * batch_size, TF_TensorByteSize(label)); + } + } + + // Clean up + csession.CloseAndDelete(s); + ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); + TF_DeleteGraph(graph); + TF_DeleteStatus(s); +} } // namespace } // namespace tensorflow diff --git a/tensorflow/c/testdata/tf_record b/tensorflow/c/testdata/tf_record new file mode 100644 index 0000000000000000000000000000000000000000..6e16076bfb79ad8151952e96567565e8820b0f5b GIT binary patch literal 417114 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

)aHlW>Qa1lecu)P#31X|&%sJ} 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±WYGB&pR zj&weq_x#h0Pp0wbD<9A8NPWV|{PEt3Z?cQFXFZqr&ydR%kh|qVA6M3-h0hM;9+}d| zuhQ61lh9~;rtX*3y$3Ohos+dHo9?%_nsh8#bwB<1x&xP!mgmH0n!V+o!gE{C#M7%* z<&w?xy&}tRv&zqunDItM`@yU0Tsm3XF1iV}2YKdHORe!d6nSptjNE7DtEwvs-kyED z^VX_uv)raln`^h&E7ya2#=OW!X?_YP!o@V){IJA4WyQehwoF6@Z zc5x}B^&Q?dHG5mgm1RBpljfCjd5N?*DzTR`xqnyQk~!y^)6p=of49wbKH2TKH;wgN zkmAzz&vo;)l5+O7?A`Z$*@autEfwZ-JF^J{Nk#mb5Af*g@oG&*GV~(?8H>OQFZX?;n*+mtMYe>&)?h(*F#qhk98)ojmqs ze>2-0qs5P{n+o?9tL@Lb8u)(F>vLwSg0{Ab3JEQbetT41i{)ceq5ZNoX5tr}-18Qs z@MOx_*=ks>5nT3Gwf5YRgpxHmK^yemUkeY9of`CMD*p-|VMfhH10lx7#eECc7bxB%BoR0AOT=Tludrx0!sPgK%$F9w^+IuqkgJ_)H?UbnqYiFhc+2rW`p zVvtnvc;r*rzpc;v#Mu z_r~vmDZ3SqI~{lwetb@)7&80^Z=vnMg#j?ooRQEv@lP6dHPOWX-dSWz3<|t zwB8rzn|D{g`qZR1yKZ~$uDSI}uQV%9I(*%mP5l8K>bE+NR3GViaavNn*Z=wc#~y0C zJI>Bqd}`)xtCzd-OlS99-o3_SLD9_bqMi{4`3&Rz7A+E)Xm_-w@uW(^LE)v_1%(*W z&S`#0vCt6-nfg89?djX+_eVRmX(zPUU5xfqpZG?siN7Yzk=aKldW)0V%eP-e6t_G1 znVHyF6~AJ8`N}-)P1!xJlS|4LFfEz*&TAUW@$(bqb49atc)Z-q(b*Ex5LQ+bB$is zzE6zRW83%Fvt-+{`Ii&hqynCJH9p%=Eobq_pi;!+{0{cXjxE01<+GVD+g{%i5Zhx3EOmAlikCBj)6 zuYX;;wA10eTuH|(bGKWsbYI;Luf0>6cGOqPck2?7ZJTdQa`QBtlxD=hyz|>k=P8Ag zFH72M`4%3q4qk03a(UUdz~G>*UZ-=nN#rtaaa3?naB-=${c7YXQk3%fi&9tL&4x>N zT2*zV%j@2pPJ6rS-Rz_G0kNV<>+ao(Y~6O_&rb`L4jYHYW9n9=rhkO_-+r&3C$i7F ztR;1o!C&u^g(4X%%6=`2^Ze3k{N=UxCikZb&0HT`9!xN4dUZFp+j#x+FDu$5S~*yL zCtN!8`f7O9&ho_@a=Sx=G%f~eJaboF!OFDsm{7}-6*HBRSkAE7SVbhp&fZf~*uPeP z)`QrSH`Ul`io+&uxpd+7%gDR{X{Ugx1uPm43{TFK8Gn1f!0xDcR_yKl5BGlvUU1Cf zOOMdLug{o&^wlQb)7IGjwCn7SS1)>fef{n)jFR)J=UOVNw)EK~`<1g2wk($@d?F*n z*0p3hQk$OcZqLk1JG7vb?rOQCDr}TfLN=`Bq<7|BZ-ZP2C5+p4WPf;^n|KV7w8%K9FO95a=ou%%O@Y}T1usPMMc@_)Eu{$1ltQt|}t z-CN!Tgf6`Pa#iNt)CjJW)*_38J;L+;{;Lt%bobC^G3k2y*+E+qjQ8EEzET>yMU!vw zl|t`;snwl-FxKqBV!Bl;_eF4m@!ueh3u50rZpW;!cs_wwyr zgO|=F*L~M}v~2ax^0KhHoK!z$vZ9s{n~m6_MAAJ6BEu{ostSoO}sky_qO%7>lQDYVsc``)kRO9zTCA`wWVd7 z*HrBX7Ax5lJ_Q)mOkTJ0!}PyK3Tj>_u&QOw7p&3vv@S&6U)cMH>G!my%aS~varN$-VxWKR)#=?Qwk-_(vQ}gJE61ic z)lDM&bvG@prdr6n<}~J2|M1~#*pKsHldLO!JX(%f%?X)$xFFgQGQrASz(^>np*bdb45SqA6xdj?U`w= z`%L+5KNdurHeGvaTk4s*$}Mzim6xizP2H0NZ*LiYoR;1d(|ogk=XIrh6>XWb+)^$b zc~eyO-Nea!E7xhCkSj*hoV(rkFiogc5`M?7aNzxI-@H1}X$oihmYCaK|GY^0p=N)P z^J-b~3E!VbS)c5(niX<~HA_RrXXT?!PKq9Ln2Z$?Y7$;avv-`ac8c7?W?=ZRcSWD{ zp_FOqv5}!mb0>Wcd_Dc`cd^Xapjw4RUZ)o{pKe+r@`uUK{r&3PqqBBxoBGYvUPvt^ zI@G%NT-D=qbAFo_U)uEg%8JEl8}{@cT6=V1)|07y+J6;Q-KT$hyQ<`mW;)BAxfaS2 zUswH+=eZ}n@CY}HU(89-_;2^U%KhHn|Hj4Tz0`Qhn`_hNXctV4UU8^1xOvJ__e;XE zOBO#fE#LjvtZajz!p(Lc-t_a+v=`osKP;mZaMbB*{HJfym$QpXE=mV2dS1Py+xFLW zpP#v+9M5(wF?UE+KvA{*=~?ANAj zi`MH6k9xU6Pn2WVJ{Q$18ahSqn^aUzu)6PHK6z=McEJX>W6um17f-04xy**UId-0^ zjM#4dAjkXnr%J|HoO7~vryluwOp3$7Z_eu1ef)pz zdp$Us^CNV!KAfr8*XJ3^ak4+huG6X9tu`^8dkYC4N8*MRn>7Tsq z%sfZ>b|+t!pQ|POGHCURUyrs~EiYPdZ|bz!bwAf`4_P={DreiO*NY9FGD#KaPMI#r zscjVgE@s7Nl{w6-1=6OT-IXel(7asw*Yi(nxYwo_#w4wN+PqZcZD`y(t@ZyIm=^`- zEb25}J6&=G*LV46Dt%hV`Afb_?YQIcR&3gYvU|MOFYeEZsz^&?QE2(j z_ur1MUZZ+_)`~w?O?zEG_CH-$A?o>Wg+h5Re@2{zlx6|vhm*=57ChN8?^@}_`^RH) zox@+%Uc7Sl{0{x=zn7jbxVtV?|LWB{dZuiP_CB}Pd>%V#`CLU#nJXcS$3G7Pc@FGjiFaAXth1urSn}%ZF=>hHKD1Z`7456EYtAv zR#X?N6g}TCS$J;r4wrzld3qB&W6z!LtNYKeG~mar1JfmRO6B>sGH%{we}#XA-{M=_ z`n}d(w|@6vbwKpW<^E!so(fgn;VDamIRAP!uSk0|U8>~v&V1R8Tlv}9C%R{(DxcbG z{a3P{Z{NIHv&%twvN!Ik{djdVqfTw#f=_7;9a^qh)f|R;o%0^M?D-@Su{cHkqR5lQ{M^Fq=>ZIq=c9haI%!H2Ej%uA znUP`ACWU6n<2TN~;O~17b!g+c;Q4L$=Vh#T$CF%oLax;J{r*s&{|ueGy|?C0`=06i zly`T~Cf=xLBFipJ5IxMU!gX?0C>N_SeYvgQed*=J zw~t=hbL zb?1wh?%c%TJg@8LN4A71eb3q7_SC8R(GL(IN$h5cJ zyK;65`cCzDq6Z?F4c^D^h#>VnckMpmM?Uu2zU zJ<`p$%yi}D=(*1eZs|);da_(%@w(N8r@i&I3%X5F)re#?n)HyXgf&gfc=2)mH-*jn z8yDs%>8#aQ=5(!T!3H52gTe=elj_pX|7R%tY~`!FCq2>U;pAm=!Y|1esx0Jw)auB< z_oMTCL<=vT3 zXFB1y`@Q3zFKy%MS2+DTN+e{Vrro1D4+RIK^!_`qyl0jOIZd^*Q2(dVZ7H#mrTS8N z)%$&OqPM%OOhLtW>OH?5DZjY-Fju3}yjuG?D->7k6HkoR z)SYfQPtk!daDo~~v*n4OSGGG$Tv<8mV$zkPGZLf@wWe%pSkBbI%ymx5#phqfJN36~ zgWN)8XPmBCC9y>>#98HS}>wCUmy!P(R&g7--(~dP+mGhX( zW*Y_;>=22OKl8|Aj%4%O-ye_J8Lv&roROttRQbN^6Kn2`F!sz(9FM26GwleCxdT>1m*$<;k^D zy9z#u`~8a6W>~Ob#Y*D|juy?o>h_s<9bV_ucaY;n)wPtK9e2_u-d?}VY}Kz@vyW~y zDG}fL^q#NkwRi)MXOo4hQ&rq|nYeDfbBZT1q_X$?`L&Gp{4siyn;%9^2@S8^b!bKW zjVp~?KH4o^qTakGQQ_maIecpT=c?V7wcW7VJd^FtF~jrctAswr+I^6eS+(JB@L!jE zW^(7tE^aqF`*QBl(k`#`RTkTh3WX%>+B2E0qKUnDp2CT{6}77vN|SAiCmH{`VtC$o z!iw2HuJ`|ESl#>Qe9*7iWqId=z2j4sMNi8vx_NTPTE2R_wSn{U?L=B0$otKmppvp{ zFqoPb@elrO}{9AY3cU0w#m0|SC>cK+x6vo=GxiY z-ha9KK;N`(nTEhtH-Co7&zrL>=d{anPx5XqdcR`(k01OoZ$B+~pRcQ%80IQh9lnEi z<@T!T%8i#}bE@mkbZKq*)fuojXDQ3^EtAw(7@4=rJ?M2+N#0_pEZi?R@$*@Am+hBt z>6L$4@%rAW(2jK>(0 zV)46u{Eyv2`fk6DaCH{t2@5_EC6ntIbKv+n_oPyr0HIkb$``n1u?KlP5Bv&RLmxC{ zx;Crcim+41{DuuN6SO8fTH^E5P{lq%>wJ~m;l=L@qVuS_|X-7q~1V`ly8{v~iP zy6kFI+{xwt8T!iRdixs&oJzQ6c4}G z&+X5Zp7^`?&0hbIL$|_B&Agr;f6Cw@;4$+};i@ZtBu<$>k6GUlZM`8k`isRMS97y{ zPh)elyVosVv$9}$bI6~jgFy{4T}h1p8P>mEwO#08i+;Lrrpo)qF8#)vXC_Uyd>3T* zGUlsn@zPhvB6xQ^a?sZHQjL3Tf5m|R`0?I&-L*0YZ!S(@+;RSa=F;nF3xmYZ_xq$R zTaowm&E+c>3@)Ej_2TenE&4Y@z&&IB`=z`YpS_mWeP0?@|8jd-@wW*IN&bOb{qp{< z4o~*o{*TX9?ZL{3!Uv!E*n_6bd*~tfa`iNh@SpBYCWqVaOr3JLva##M9rK5Bnng=Y zBK|X6&kDZ3_V&FOxhw97Toqa2J#Uu9C%F|`!mrdPs&Fze-acQmZ|fr&OU4YR*RqD^ z7S4V!Pn~H}Fw1BAb0tqUe0kZbHsh+@wATWsLfU3!2HfIunl#U)P)%i$`i>LLOJvIX zjJ;Op%BS6au9E+-F=Xq8Nh()9Zu{#bN<8 z#4&+qa+7~9*Mu0s+dR`>Hg4a!WI~~zrpPlP-Gzs*ef!{Dvu9WHRJq&X3m=MC&HZf_D&5;>_;qQk+RwEs z*R2Ybe`LX0rRkih@I63Li?hRW#=Dwi-HPcP+}W$Q2Jrj5)4DFYb4%RSi|48s-ELQ1 z|EC{u?s?r_z1Ru+IGzQcec&SCa=`9}Yzy;A4pP}tu z%AsvbtB9@M`TLvtx&s+EA2-hO_|n9#BjkT3TzTKd?e0OZCskeR z-S$4RbmmkH}_uuRA^e12|oeeR7TXACDk ztLF<{GV5!Q=3@7v)OW8n6@GPvr4-u!E_*-kgWozEvwz0TdTW(`UawkyHdAK8nwAyn zw(QRxet-I2wRYo9NAJLZ!;M+19{qOo+%~aeRl7j{1E&Z@bBiGuOZjwWfVSLvqtw=$QMTil|rHFC$)WR|IWQUwW5=y$o3BZ z%zM-0Zkl9I*5IqkT0iUD$~~{L)|fq;vm$zx&J}~LtGid(aY%$Gv$+>feDFHX{@ac( z9!7_A`|Kaz>N9Yc5_)_3@YKwlxW(6Vc0b+rLhP-!?(r3|{%bU6S!ik)pO-zTGNIT~ zg=OQcM>UZNQ*Hmeb6-}zNOwo#B-`!mho?rhl!QIKF1I?jI^HYm)*bF0fg58>cWl`f zyL{XBci-&-Di*Y?G`O|oiJG?t(5y_4N5%@7`bTzsz^w zSJf?@o7;M~P3`WU6SL!v6GPqBODd-)zjgEAJZ9aK9DUJdxyQT42Zg_DRyEcaT;5*t z;791H+!h74TOkczAwd&4j%Ut!&bGK0}{&j_IkL#?#+6P3=cTPWdcV(oc>6IrI{iZ6)w@DdcKtrL(s7o^#6B

qz50~O?|yJBs8b$E=smfm(x&6S;5bLY3;e}>qieeb>0d_6-|Euvnn+<25n zc~?kFv+jJgVr8El%`q_tml-hpOqx(|nj`VF=RKu4(#|Vo6?O#2zUiv^o$neY>K*Xa zTRV%9L9cq+pPp};^S4d-yYQius=e~WugkA}`>;Dx-B&*NOVGK69%n^Q%?$gpW&Ys> zMpGG1PJFA7QpkCtUw!AL7Zz*Ilqg)PDT=n6XOi&Xu+@!6ebcv>i{0M5s6A!t_RLkB zs!wBL3R04%EVWd>rO?1uA1la{w`=K4hP)Ob{!=qM z_$X30`11M-sWYc4TCy-DXclY9Es z`}Xb8(xo0e(Ua!;b*d!Dy9l4*kO)-%^x)XkwZ|gPh;vl-Jhz*#e- zo)&L!tCu*e&#F0P#ad2=%Bf2|e@;0*QM+Aa>y?vi-uec2?Lw^!4*zGE@T59$|Kmwh zRkgR&@x2ed=iXLgs3`qHrw!G9wz`vq+{qgrACBDD5nu=`koW$Ly zB^1J-lF}*vC9H(??7He33jJS&*k1-7>r-Mga^HAwhxXU}uhXyFc7AoNN%Z zZ931kN2ayzBkv;kIc6^z>R=PdwV(#ux#wAOmeb@S%1)e`7V1sC*{iyPuxrfn~=6>o^JZ)^+#>COj@d!SC^zYYxjT zO%^THJrI1<#^aG)^6~p`rKV-{^vG7#?4LJv;Y?8M1pmjWX2}QS^QitE%ecuE03S zy8g25zS3PQWKQTl54z)W!puZH@~GWU65RrMdaQx_w=UPh?@e6QlK&=#o`Ir--uHxGj_efDXU9Wes4=j7DY zyf~30aq+~@e_x;Hox56E_t>>`<%;{+ zRZl&bU^OY*<3~x*HkIjeSKms%`29jxmibrFjq0E$zjMAOEuQ10OkWx%(JCWAm%!4qB(0cUggF?gN+cj5BH41ys=b5u2g{64{`<-oDCp3H%*!irUch$mz?I$%HZc8<%FO#nN_w3Qu z!0T7Szv^8K*jTkDqb;;(WntXrexsWzRhiMNa#VU)_$T-rs$6tpIt#z=e+Dm|8;fN& zy#I0SzW?{I$1ihF{;ImF_c}rUKf|k)EmOBC9A4o$Pm5)yha|&9mgbdl zv_fIWG)}{=eP-3Yi@xVB+Z`=(HFW2Lsfr02st2r3C^<^V?t6aXQdyhP&5j1!*2ogk zp5&^(RlWLV6Q&rXEIDYiiSggxGyfUZm8$eEGg8hyZ#?JPi(UJ^YxS+(w(XOgqs%N7 ze?x}f%X=PK^HdrxNbGZE-kiko`p;7*=RB6Boj0@QfBpU9@{+$L3wEsgx;=MGbnCTK z)1!Zc&3&$={&VdL5et=h4_5f}GFBg6n>YKdkb7tNi668YwmQBrr>u%yp~f{+|nOp#L0eS7kRiMy!%ng ze+Hr50yZxff2fl`GJz$>@`;VT)}#3WziO}4O^pfl-oI_`<2=y`i-Uq>mRB$pOYe*e~>AIDktY*8(~^YPdf*UM=ywyQRNiz?mKJ=K2a zaVF!V75@7S5(FhDseHd=->o|7o@;{1+G0OtjqQorW{au-pH+<>ZoGyF$hxXK&j~ctdTDOnyG=(Pr01kRed?&2d;8h>9andj zEM2`~+t!!A?u0Qc7k(J~&(E;$mzDR6casGScrRR!|Ka!U+P&1iKMXsbc^s?FoSV7A zY3{Ne0Uu4eS1eb|h*_etRx?0l#*B&T+w23wcQ-}Mc0Fj-VYjEZY|0K##~p4`?lI41 zOz)0ewCjEG>W!jHS?k-K^^Alx?|b!#%v8U8;L&=U{sUKQ_2xb2iYg2ID>F07ikpEY&W zN&ectpKiNuySv_(mDRUuw!`{DJ(EjwzZ|OVeD=v_h7F_o#;ZBCyW^%z>{PbYZS41X zwj$VcjmPR6-#hOxPOxEhw!HkSHP|#Sb@g1;`&ZWHu1bI5tm<=l*<3y0sZKk?MEYMo zy42G#De>*8hf7%9`y9WWU%r+#|H#|0Q1?!^bS6OU zY<##qN`A@hspe+?86+L^@8>N%&>nVmSF5PbCCv=)c@s+g7OZs8RgJsIrBX51tZZBV zZqrlRRTJmi@a_w-u^02q+1q`4&64^FwFrgc*kw)OMc4Ie1Db~p9tCeIxlxayKrcz*Va$oy~})9R(P=Z@v%(2 zG4I#L8y~zY7G2@*E4trT8T8`e)Q@Z3Y6?&P%TV1VWgwS1YZhyjXQ5W`iKqkq$!D|V zZ!pVlnRSHmmdh!wZHL(dp1%$*cjdh=;@Lt^RrgQ9KG#T&2iv(TuFaP=v9UAy}7xj zTW&<~u8@k5^PeJlu!*N!u1a-7|EAwAjFoA#odrr`HRr~k_fmVA?qq&F=KI{)>2IuD z+F#v!8uZY^OJkN-LvWu~0)zUyv~Suc8~*?#|t+alp{w^#hTo1TogB%1-%dLBeeVkk z-G~&fl>0Z=t=@cBBX=s(yuG=)IxCN?k}B!Xf6Z=C?RjnOAtfEzJI@bKvpsI5{pO9Z zoT=-t!i#tBX1@yUFO7YnwL*IPx`m~sqI~VF2al?>PDu+rneF+cc7ckTfx^tI4wrVX zU#qrxHcOZJ+cHCGQnJ?{0-JcvY*xUHYFvd|$`j z?JqKZrA@tO)#2>6sq4w5o6EKAzBS+X zVBeb;A4Cprsu$^NySH8LYT~x_(`@$!JU)9TZe1qhu_+ABF;U!sK}=1Z%Dh!M9-al{3o&e_BZZr z_iz6-yj{loXlwuByP+|0%a-MZaL;dVSZcxHE~p^js4ft)^vNPN?;VZo6N9(!jecXt z?4HQ&eaGf(@qdQp)|+;o-5y@#_Hu7f{PdWYg{Q*1vx_G$eEy-8KYY@0qm=4~&-Ydz zwb*+^CuNE@=eQR;b&sywvOglO@WTv`a3K#*{~VVN+1)RbGr!Gfj*u%)oThjB3wLWfJ|79J2=-0y~eI_S^^_o06 z1tv8yGI6E*?~~sX(jT%SP@Y^}EmNDa$9Sv&*a0 z?|-{8C}Xa*nbpRCohh~HYxC+f771~(F*SNj;5cW$|M=^*L7`41JzPi5EY92dY1NTw zm!@5q?o@pGxPGVlw~3!SEv}_i%nZHhdA$93^t30eb=}Qc)1wuGRs`*EWa((KsY$87 zAK-iQh_hR>`ibVR>UKBQ>|y-9Z0o%>GVS7wkrrx&znPo=Zd`NcprLP;mP+fvL+>M; zUl?&oJ9l<+`LL{h9vyviOZSQp@tjp`Eoa@YG<=%o#i1JH!lBaSD0n|`@9xdXt3G{R zDHof)q(^GO{@_~g+ESl4vx5&loe}(Dp?1gtKMod0Bb#KYuir(J<)+>I&){|W&1L~> zz1W)C)u+_2^@ptbweae7iOpek+WAq@Wv8>W0^%PD+`jbJjpy|GQ$Yr{X3{^E{p;kn zSDy~LILGi=mZo6He}N349seXNG)hDmU6jl};R*m8yu1cMm zD*qYk{+R~1HpE>xdH#r~+)cH{i5J}eGxSZmo4WYU)@`#rZg=kpx&5C(C^YV<(3GzB zi3elYw|rLeoWP%?F17JPd8g6OgSIu9p`SNDP`eruTM%fLm*pBQ`o-TYMpVmY!V;ZC zhZ84{ADpc0w_szYf4PM9ldng$gqwc(8-}qRy(E2f|Elc!WnTk=Zrl&vD$4sQ=x$-Q z3)?A<{xeLSvOAUwTgCLKy74ZzU&=MdD8252Y-@I@+vd_6`6t(H(z+e&`J+H;`Ko13 zx1twRFLfwljbv$Vuz6{sY0~92ajWOiyTQzBt<3zMFS=`8)b-iZFY~tMmun~gTshaU zVA)kc87~G23E}To_>RurJe8UMOW%}m-ili?E6<9q)eNf8)GT$F#H41g-+8?MI`i`= ze<8P90m-M9Ws20kD_u7~Nn}c!(}lvLPV70%{~7APTs&Eo+#e~kf6gk8#XP0@dRsfc zMFgZim@u_zjmXKz57`+WO#D^%>+6-ji&iT$MWcHORByfyai z`2}XXs@j@+?Ksu%Oyc?Z_OtMu+)56GvcpQ7wrj64TF$nU^Q-3{%UipGug6i!Q>-Sr>ehdV#KFQC(T zqmcE^kD~Q9u@S!*wzaRym(^Ie^UL;9>wvFTE5f#m9QA&vCJ?1iRp2haQN`t(al?Ox zkoSwGzqu1-aFKVDn?%j$uL~^Be|g^ik-I1{aN@)HC$5U!-)+7&R%uPy>eajDwk^)x z_UPsQqneV>eLg<&YB|9fRLSJRVPP-zs>behOT77ihRb)0s;WL6E%Gz`;4&@j*J7Ra zX$2B>l5#)4E=%{Tnz&WFOzijOh*dh_RjsM?3#&-c=$~HMP_y)u@yo zQF-*Af&VPmF+C1-%kvNS2ItJuR>{!&IscP2|E(q8=Bo0&)W4}c^3R?==wmepdbGCsou%JIi<<^$h=>zUya;8rCa6HVFF<3VD}?&MEm<}+KeQZu;o$y5*T?#V2#XV!!Z%V z>&okz+2JLs&Yw=0Nc=m@qZ;X%cwygs-;MjXM(sFzZd;S+j230)oX2M_&egbUZZ`MU z>!MB*rG?qClG}}zY?|H_w9KJYD=_t{rh21V*fUNQ=4a7iZ_>?PO;SAOZSLnj&uqz_ zsC$=qJU$ty&o(=_@nO*R>Z6Y)T`t(YXFIpwvYl~Dliz6aUcR!eb=%(+eHD#=G;6ZWa!Ilpe>Sr(xwD;Uyw)~t;?cD1>&&3e-Qf*-|qYBk?J7usTY zxKxAjenMsL{Ixoo+x|Kq{B=tA_|BElwa>+tYz=t8(kkNlFJY1slFPWfHwnmcw+ zywXb#$St>%SabGBa>jvBmi4w9Kdxw?_qx}ry=(W+z8k$zbjr40`d)`)J@Qu9 zOnh6v;p?)|iBj*85*G6nBa3IQfBD@=yD@ja7FU4)sMokL~{D^($dR347Az z%PXgZzR!BH?dhc*oLV9O8BXVK|1^|#(zcr+uaDc?T#8<6ws+${ zhGWn7UU_#qdJF%jCF{1b{!^Nn(!lBDIAx}VrusXR+RdAuD4hCn!QAke{qEE9=R&8g zE{xUKmb}j&d_wfG=98Os=Hr`peo7|sF@#?VIEFn~C&hyaq_;>ML zw)U|vq{Z&S2zx7?fi*N**X5O;d^ZR3?AW=FF`e$=vA{AW-NO7riTQ#Y&Y z%Z!5|RyCFrYTXR%N?eyrm*AGT{qwQUxwW;fAz#dw=)CV+oNIe@M>+RZjm24a6PIa4 zu86do?6=ar+xV!_)>TX$LY`OkJ3h8<3AR1JqQ`eQe`)*<_QT)97aUn5vh8M`^L5Gf z_H&*7seb=HYv-HW@_sM3)jc&8|qXvTKh%wOwl9e6(be4<|djRblqW>O$^f z+byw=PfT9&o9o3L;a`rjiTuFQr~g^J6kmrwupz3k?WDD%8) zi*+yAOu4{H$KFD=wnE?Ea0r(f78^+mJgu zKIrAkxHa3CO>27dR@t;=sb|)viT|_|)qnb#pJbjCI-&FEwP$xYw)Ql!6;7Vu-gt~- z#ezp}$sJ!7R|x*Rbg924G)iVgwyI>Um?QN_(!}k@mEwM>T?H@rv(!}5@4ws{d2i-DjV!$>;oNag z{T>%Bb$r#8VixVp>1V+@`GKE%;iHnS{|s%h(hI(G|LtqeKWuwzU5(S4*C$qdy8rQL zmGZCDUtP(o#j<|;zB`_F)BJVx{I;YUXOqI@mi8yDy#43tmX?oN3VhCwtzMXk2b`EY zdn)sSt2@4k9r|1n1t?mGRb4THS7E4S;x8_ z)ta33IZN7jq>dF>-=k8sZ5NE zjYxidYR_>F4`x&Oe+C~5nA$F^P&rg*!&KmMy!hM4m5FmEI5>W`UzXugW_0~@xQn9U z$;2I8|JDdvcAPvpS5&6&XSr1PQWrIiTj5h#OSM;A*{z>>C!gO!YpZ|X4yY=rx-pU?(y6BmvdD}zLEmJ~QvHWLf-8QQ< zBs|8WXW6uw+hZDy9?fu=v~bbGH&22}*bYkbRle-kmzs7@GR`L9L8;8~wUyu9uHO01 zyVrAvhQ@lI%Q3I#Y`-x(I9u<^6qbHh2IdnB`OHGuxl4G`bPE6Y%LLE3AzCWyd8v4N zo2KuXy!k&mk{?xeeV_bA_RH7QU#`DmUS4;TSsHk^W4Z6;qEOan%RUC&C|W+v+WhFQ zlBR-bJt=cT);%pQR^qkdmHsxhj92cA)}8$|IVQ5vFB4WOh^{=)^3A}}cCrD(d^wgE zMJ|h7dV9k3a%X#36+3*lTV3*PX4=H+%{faQSw8p%i3KR}2^9(o)!FYlr&=!(#L^Zx zZ+F_#{&ug>*yJ0RF5LemTzAK7{hD1j=I=K7lqC~&>eA`H29F6F1VZ?QZyXf9@0;}F zm_^fJ)nk+YS;*V`2w4B&%ir}`!VJN;=KaoIp{aB2@-?#n&!{ZR2ZBF)o~3_(yCU_G zzy_<7EB--cKLh4}c_gth>S*PX#XACaU5zzZndtd#aZ8``4oBPN%+Gy<-Z>p|FklZp z)wFYSXwNifhrbqn=hB+iJ=}V4{*$o|MV(9zOf-#z3B);{kO+^rcJyb-SOLR z`LQXtUvl?d*eaC2_WXYa?l)&o@3>huk?+!l-N9#X23aljgjZ|v zD_Kg-dhyL+hv?@`<{o#ev+fpX1m}y0h6Ya&kPduryGTXycjH<;#Yv`&;aYqBquNfr z@|>{h>r(L`^J)KHe!VsC&8k(>ZTD+dYGxQo@#}naNop- zj^~d&Yiz6JD!lRTZJsyly0o_IBVJLAftdSt&GYWJ{zRCZ7h`5H0*yTc=@l% z$+U343%sujUnPBb)Dpb%fTF?{+XFsYHqSfRkF{4HQDCzF)%*9g$K#+~v*!l)uUNn2 z?%ZouUc2XR)q8vP`3nuDty2YN9XxXUQNRRCmq}s@OU^f>*y$!`+EiRz+8?}cecqJo zt%W~zBSfEGudBOyaNnA@XWw3${rB?n*pS<9>s~$J@V?r+RAlRw3!WOa9*R%A`kxA| zILVSetI$vAj>YOJ9A~PR^6}mIS+eeEV!iAa$Eh3kNgVsDJ@;?wFT0oeAtI~a`@c24 zYjXFhzvlHjOFVpyuG(}jZCE2Cd{mXun5iU3{+gcl;dMuX_^#P*GP_eL^W1LH>OaX3 zIPydn7bzECm*n05_+iYSyL07!HN5QD5q4{3_}(`SJEmOSwCMAfm2N>VbLtH@u}FO9 zNf%tV|B#*KgFPvoveoA532x@!PMDslcz@t>wBO!Y;&STKcZlrYlwa-kzZIKx83!BBQ=KvV`(_pTF&CY^dQN8p$S)`OLdpD3hd|zIAE#d#`s=%Dce!?e|JvbJbBe!@pV|7{9liGPkF*8 zwg-PX8nJI?Y2E429l2R63;l}(w9QhuJ%>&I@BcUbOT40-IG# z``6rN&$|0cpL1_X-gy?)duom8yT*k`!3 z^7mD%JMjUt7k$w)HH+O@z*)97lxyWK8)wCvEFXB5+I{j=VR93`WqLKIj%`bu!CH$O zc?)>{e9D@AeE-^O8&2^UBs8AvJAC`&(naR};aOAOhORGo>z%E)X2-IlOZC0_CoNSx zgRZs_lOowfow=zri|E z*|`r{nY;6*wyag@VVY!bGxK1_v69-qNB1tgxLVo%k>URNesv=2PK7Z{l~BHte&brN zu0v*b^_4Actr}cja}`~7tYDuwalUQ)m+zNP?C6fwU~<~QUc2S@m-DT>MU%Yu9QUj2 z()5kJXtM4jN8yEr*Ij%PEE82GS$PI5f4a%O;n1DnmU zdZ~u(wsF4`j6Fkynk*ExG^YwatZhGN`=4Ro5}WehOP3}Ex|ay~Wo6_poRaXyX4|@D ze+5eTw#}P3Z*9I*$?Q$ty>DNIhXu_NOA`Cx*&tKEG_%pz_^s5Y8tsG1t=5_1y6SgN z{w)`E|1rD!*z0eq7RQ6H?P_+re8%V!SHLZ&N_Ga$;E48=MRt$vxtVs6~L{iRdP@tcZVm6xO* z*1a}SdXwAwO6mwo$+8INZ^Wte2ipl+z!WB9FC+Dv+x@&1jsCbG}= zJh5`^)+@WK&n-H?BO^3*cQJEQyU1h##*ham{@m?6XPvS8w8PnnlWH~Ru06Kw(%Gdi znqFScom{qBoa?sg(pO(rYV#{D=!kOiFzAtx@lRG(e(+*W(zAQEM&CET+?uP~CI04# zMBxKX=lDHGo2%@dD;KZalP47wE}1m1bE@v`UQ0!fI5vw3K?^({_dS@)FD|%!s!y?5 z;c034=`RD<+?_1gSM%b2Ua8daT{0`vX3hO2J=bVT?6NhE&MTDvGc1`YkkCJqJpZB+|*{bzWz_THuUn-1-!a+;4z11bOOuD zp!H|`pD{4Mom&{+QPDCvrsFFEA2Qc^9l>s20oj7VwKMhe^r^L`WGhgBKuly>(nkxvP_0lIFg`GK zy;IX9OM}aoIi9hbtTIsfX}kRU**(XkYn9*ie@&6k3Rc@6V4wSwc*83X?n}W*(UvnUm^q`QI(W$@7<5 z?Ag%ubV3i)o&OAvwr9S(c68}PpSQKHM>QF1=FDVudFUx2+n#yOgxlG8vgxh1o=^9s zuTF0-_?=z%Nnlrv*}1ai@1BNTT=Syu-0tAqmfPl=^LDK|y;b**-RxHNb(M3T=aBXdCC+u4vAyZh5xQSbyfMK{Hp9w#h0I-L=L_Jn!-7W{4UnSZKV=PuPLD7^5b=1*qrW}6kE zw}XB>33po?oBV3Ix7p?CYp3shnH&Br_o~e*P2C=;>jAI28uWKfU8|zfqOwGdf#J-# z)qj*b?%a-9UTE-KYU3pKyT z4Z1OZyNSlD6(ZX$R~~86C@OUB@>*7*roQK9#hcCD4IBOBC2y>^SL_mR|DbU|G5J7# zUEEgIP4Ro*+?skd;Kn-Dtqd|ND&x;N_nA4}(PKWdgX{cmwUl$|p^HBnNpj8jZE1gN zalXxC`=}jJx1$e>b=EBi-S|T9r>w%A<7-dti2mO8aI?sNhPK>Q$5)9*7DaM7UH#E8 zRc-3By+tpAI=LcRI_D<0#hv7NRHiuP)76dk%lGDgR7tB~=WS25f7-Zx{o7c}7vdjn zZpNnEj}Bfw{omZXW}&$U?c2;Jm!#e<4*fSXF#nFw7SlKVp)L(xj*@d@*X;OX9y}xE zP-XM{TRW%A{F`&h9#!_V?|CNEQ%4V&j%Enqp7 zdi~bMR>uO(EY3+wJ>NfO{&VB~$F&a|y&l$m&0Bps;P$PpT23B36I!+uvI;Cp^Ev-g z_}{FvXZLg&Ecy99ZpUDQmfM7&~kd;GfX`8{d7 z!yZSEXBY*imoC%R?EWqmRjIeHRWVh6+vR?<8$m&fV!U}zC=_0w71pQlNr9z)!ii&b zQyg~furdJNdm~(_`;iuY2(~_1pWxy;d*1rvGR^>DAQU@-Jlh7Di@?x9il8?la#%uUsjh zhEYP^uxnpbfyPy%?o%_utW|ei_7nN+yHakJS3~$#l}d)n?+GsDKQ4tl3c6kUx>e`S z2mjYW-5K4P)0cbQSh49+$d2_19z~8%X7rr=_tAL8^4+_+m&Yx&`QZO}){%+Ms~zsh z=O5eec5luVE8ur%J%59 z`=Yns%fGVHLGIXVu_=p>OR#r-{q{t7X#;S-NBx)NRf`s${4 zPoJGH-x(A$KVECOz{`l*h*nM8n^+gHrEy?wQ5>edB|wrd|=dg1Zh{|pKqnjV@SohkzS$?F=~ zkB4nOqrCU&g{H#&z2V9~oFcBrZu{}|&x?OA!!O6}y}EU)XyN|7dD}y|PIZ;d4(8aQ zqOw5IOJT(p?zVtQlXi4A&g)D#CBCKETYcjF*P%0ie(S*xqHgPcvYA__IcYHbJE-ApaXjfmx)MY9Sb?H8Ac)Rr0 zaNB*au61!QwYBuMetU4rE|ryA5BYDIz%R7$aSZ>X^=)}NOYAc`rPO|}vHSS-XvsU? zwxJI|cPm#)H2n*gc zsa|>3=I$rDb1m#@RyEan|K-hH{rIYR-JGb_?vf6+nN#HtUR%e*Fh%{Rwh^bwlo{`o zW9uLKzuNnB&LZwdCiB@?eka#dU6t?qqb&QnbJ6Ag_{&+(g2Sc%o!oJ!%syb&j$86u z4W61Bi*f{*oNF!qGDYFUB(ECx_OzSyn|g1M%!InC45^l+54@!x&>eqZ&==gbezqGtgt*ZbD^&pePS z_uSO!w(GNn9I?Evh07RJHMvVf9NtvO-?wbCaoaWf+|BpAe>Qe)J$NwpYC)gFzlz+r zbGmPhUfFYdM;*2G?os8cyk|4hqhZn%W^GTEd9ghXMr`{Gwr^l;$vPr_UnSXp!MU$r z+(o)xCSU2i_hH4~1GBPTS>2K=dmfj&bjQ>c!mlJJgdX`_yN~mXKyvi?swl@?20h_< zb>#FUYY4Xbb&vfoA&o6T{=Q(*YF-UwU-Mi?n$ys5w zro##5f&E&AvfDO^tXov|eEOy3W;Yi_uFP8+lP&b7?G)n#HU0vg6Ms!pwmi7bCRn2K zz9@HB<)!YHj3<^aFU_+Rf7zQK@@sCw#^9CB8OxpGc3v@RJh^OArHk;)sT@oWA168c z2_B59ev%$@WM0Ysp7=FlOCv<{bzkqe<|VfFOHSa$%VH}7rYz)T{3$T8QQpEo!^r0K zI#$8eI_X+fMaP$0JquEqv>;8AdF9HDfd@G&J+x$;_8P4_C1H2}bL~f|Bjx8hr?D$9 z_?s0rOP$Hp=8xIa$V!$Lfk(0a`~@l+!u>@j8}AEMH`v$iyR+1Ohnw{A89&3MRC8pW zTyuD?;N_s8@|%gvYqBsy^81N5R=mzwtEl2Rq3}d+$C?B0%A~m*OP}X1+ny6s&dYCD zzKAO-WV(e=&!m~hm6;h5Y8M;cI`{CL@#iHres=>515YigoA>=|+Vh(SXWc)x^p*O5 z2Da8+`?7+#>lOutHY>=n2D31J@SOKda*@>9-#edKUe1%ZncG*mzt=0bYjXYl=w)9H zMT>~!nC4_YJ?Y)AGF87%Nysa>f#saFgMa!8?Q`Zb4Hm~Q^>5qA=eKt6tJSqhrHbcf z*l2xo_6^A2y<4p}TwS5N)+j~uidp@%<+XPl!|cy0G_HDoEMDZii2dz;mD1P!R=fY` zekchS61PY&+#dDt-?vw4Uw6M_y=Ok{?e4r)&tA2BwKBq!Q;*qTMq~dMsV8&7 zJ5E+=Ssf9z>iNDe^p?_t(=saRhCMv#YZY`&!?oY$p1nQQ$eC%c|Lzz3J{oIlI{f8x z_!L?ilo@y}ntN~Gc5R6}`=8;C@vKev4jsC~64*rU3;rf<{Bxj8fM zd-kf`;a8ki1%xbZ6WOAERM@etwsHExxwvsD_q-W*9<4QS zV4G6E_u=Ef+wIvA<$vd`yY%Q_Jewjik@1irt7eB_zg1RG^8D@mo8F(! z3ptu6#d?T~b#KX{zn89FzjiyXG&F3h*|ZB1IU;*zJP~;0`AzwD|GUD4m-|#dXq<8G z+jD&9ZFwz?Ek3)R^s_(9a9%N0nz=Nx{@}0fEvsHcuUJ+zS2R~q{~zxm$0<8iJylvJ zG5nja@bLQK?b9dvsCZU)Rh2H;6a8@iwqr-!Cx(jsy_mh%*LwQd#XB;;t4incAG);f zR`c2g?jk<_5+*lGeimNWFkkYv`nTn=3iFa{>aDJ7{829zdSdls>> zIZhW|m)-f#u-r26h1s8s)#ns8xdd)yY%;R!o5}JdrR2u@3s?3oPcK#L`_uc{d;6Mg zR$aGFt=?Sr)cnw_UOu}=mu?A6VsOxYFs1Pbhh%T@>hnE6QqLT_6{aK3to^uecIYHM zd5x`1HjST`v-?=BJ4+-6x!>1!4{O-w|GRc3pTo38-gu5JyYe7};@ex8D=QtF4lD933-d!@wlaVR--}=k) zb2;wtr0?TSKR4s5Xz8?Vt0tY!T)A`6+->)~6xeHn7R(Gh7$Rx>y_davM{wwg@aqqZ z>MBKbUJ0@!=aksmYHhn~zVG(tI-%R?wdK-NlcvmCHalnAVZWm9W}yZv4<2!R``oAg z#i=g605r1z1?2L~s-t&&@*4x81Yr@w?7p$$>_cv(5?c616v!-flIOlF_`On}L z?eTFP`*Az<-!Z+VYN39HPhW?h&fv+sal&N&`g8pn-!?N|yX@;HYc$Vc;+^?Bj!%A& z9%4OD>GS!S(i--ce_h^~uToKDWb=Jx*DCAU`IBU$;>s))*C|X|zk%Jhzt(4to9cwx zg{N6wxN)4CKi?KyvG_-Hm-g1%o^w7aOZ+TdE9VgH7{6BVw(Noeai$3i8jo4lB|kYa?+Am( z0r^U)CJx=X_cGb*E&elj+9iCEyV{)BlJPXye`9`qhs+L5ms3S*MxK9<@wLr5b6Row z7R|mazT=0hCa#r8XU=LcTYl-IW{}Vwri%1Q#~lA!&%cuKkK6X!;#-Cqg^g$ayojD1 zv*NtfkE~fQb1S<}X`R}|HSM8d$t%rgDuOB!kClqQ|Llzpf0TDb_X^L)O>+bs0rf%J_?O-DVXI&Y~;t81|46vboYTt6-}IiZ2T_@*}q67 zFEzS#!e*QGh3|)r%3Yb>`%X;0_pPd%?a{W)vAOlE+)ZvqOXfZ3Fjzn1VvN^4O(mw( zYy8e9sw=Cm{gLjBaaPRu70yw&_}}dE<8IsMik_P_@%6WZ(XJupW_#C}Jk;u{n)07v zj>@4B_WiP(*tvh4Zt%=koFRC#Z!)9X_1>!xj( z?yJ0Z$L&}C%eFQxb*j;*4w*2=M_xU2=d!M9qtCoK`{w&|ZMkvRt^bBm@2;z{udiRW zjt>y~Vp(?k(82wgFHVbyp55o2`*^bB7qyUWee8K2PYsgz9sbR#Nfezuo7(f2-=6Qw`FlHi^`>`Tsw<~Q7OwE_@alLX;=Y%m z|BO-TmmC>C>lG~Djt9wHXMX%gDrCZK5rrSUM`O8iFXrE^c2CK*J-BVHxb@UsjV3Es zt$h)F2o<*2Z%R*wtu@&Nb#2(#|zISES++)VuS3D_OwRi2(ySp}|=|8dh z)wko5QoroVO=gE)t(z|8y6u6cS76Yz`4Nw`6x}%+Rvw%i65?$#vnMnEtL!HGxtdln z9Gz1aC-*tC=PymDI_Mp`W&QPRE1g4+J-r^yjJ=u_ZhHJlv+~UIP3ne{0@7QjeH5J8 zB$qOQeZyn%N&UAPC2=>RCC7NbUU8HAK~`9e)PFaSZ$sD zchBlIAQSRH7%cr_*%H%xJQ2yn1 z)$c`awmZ+QnQs^zZvB~U%BIU)t5dH`SQim+d`GLWy@fsV^Ptm5!W@#6ohs%ue_HVN z;iM}Y4<7vd$KOATYxRzR?b$)Ef-Y&vgj5S26!7_d`PY-lwjXbY@rfTew{=HbEKA?w zB>QezH+kH*GprYV@~yhI?6}(OmS4HwJ5Qc3K9VB1+I3M> zilG#LyUy>fB_Ge3ml`K*h&9dEE9Z(1<@L=??h<*kWqZdHuOzwJMRO@8RNorH zFC6 z{U@lu(1=sEdieSJw5zeZG`Da6WF{(icY9#?4e7Oo{&(I@?XWqNz|d9Jp|ZZ8Q;k7o z<^+b)@}rYN?x(G^D4la@oy5!4iD?_7&89A09lDZtv&SJJ#>XdJ3#YC)veGrkYxi?0 zx8J9@&F!b%D@|xSCK5b(wQP3N{G-8ZOHFpK&b(>1H`94bX8A|H?eBxUuFQ1mc&znU z?huC&$5rRPn8g#$G^{I}SKs#aavgV*$z8^S>+&xKlq$XTuUc})`Y!jTYyTNuvM+R- zVzDhuWtk_7_C0~n`|P1|hO+ZZe=EooCC=%eTe9h0NNeq$&GV}oZqGHV&M^5}wSDi^ ze7yxh+w%%1z2en8bS>V0izB0=iqDZstJ`mvJuvXM#XRh`7ASYUeCpwIL_gWSiT34vyfTum?4HxS2c>2UrSvqP zm)V*7d3CZS*NPS8um3#tlH2`m!|UmO^Df$0H@01LJ9TA) zT;>Cg$sIv8qWm*E-h>`MeA>h)PVwsMX_kLAG%5~ynl!JtdRiyXC8z7xs?}3Dw_QHm zBf5TaS3ZN{p#(1%Aw|09)$W6}8n|)TP7;O`f(&mWp5mlL}pv7~N<%FEcq3ble@r?WnvIDcx(OY_;`ychlMF1we^*hm_@jt^Qw^i$N)0Q0MnQ{HPf_#kZ%b3ZV&oId_eqFv-?bI!C-S%DIXY9FV z7m@eok!+K=K$&<&bO4lylKkIJ6^7;P!KbIW$(+S_Q zH*iP3qJ~CPx6icY7n3Sl6Xq8-)g;W{xAn332DgP%H(r<~$-G=|-Rwss8I+XiJCuzUJ(wAsM$>zT|LeRahTwPS|c!^7C3; z!(XMGyxpaTGv404p=a%#wM%Q=)I%3O``QNxvtM#7pOC{}IAzHSMg!|tXFrs_-ShF~ z4&BKo-WYbRE0ojvW{{oH(^K{3*Y(wQ9bXPF->Y`R%(n5;GjY#NYoA{Bn_OVJ+Iw}> zw1;1>cxVMp@oFhQ!yB_Bp*8;Bebsj3Np(|C?zsEe>sZk2c{~1UUR!pph3Sz<+k;P! zX3DV(&GGNQ)9ZcYBg>8nweR&Wt)3j+VD#se?G;r;5AROn1_vYeIr2UK8TM^&6-ii~ zA~$u7k%!x)+d=bJ+>L#?-dlRReto_+-=)yvhUk}C_qtXuI{8T0*K5kk>r=Iuq`ekA zmbwh|CcKTEJ-c=XBR zS}_gI9-a-CIUkh&ARv|Iv{Im ze#n}&Pq)ZbT0D7m@}2*r;@fhjpY`P*Z8taCGI_Vh{Ff~93)js)RFSmA>lD*u|JT7D znUjQ0Npc1n%sW)gWOrcxAE`x$HA<4%1Z$+FD}Fwk9CuG^GrB6kd8Y~Kxsr2Kt zOFsYFaHVpa$n=nz3?=7|+r@?GeoSZ(KG(e3e5&=;`wbgSo}Rq;bB_O?W&O$XU!7n$ zp3m}f>)f+DrYzea@I_hj`(KUpcMNOF*GA>`?pVL>*Vb@#;e*C6H>vlzXa03FS-Cvo z?kpD8%_ld{&q>>wDbd6D-gQlh*VK~JzW$HqtjQ5&75A80{`AO^gUtKS=QYcK!CMw@p{pLA$KQ0A@Tj;OtxymmhCnJ8g@sjlSS>wcYg+>2f+vj?y|{?Cv& zc~)fIqdoN5}6q{0vMB%iK*@pA4L zhU22je$oX?ET%_AspxJO<<$KBeIi3yzwPIh`pakTw$%Bwknf*`UHsYttFcRH<&~>>ca|S|InZ zLRCdVcYfCHn(PDmk?LtT-Y*W5 z@)xn1@h^C7^G&VuZ#WK0y{%6bvf6oo{m_c*#vbe?ftzjzzg6W7Zq)WF5bcZ47gXr^ z`{kOeyAim z44E9l-rI48ea@XV_I08Dt5&~?<66WK&0~A? zXw^FT=TkYqGtEnJ@JsNVWXDi;-67NJfIZtX}zvC`>Y8*^VEtbFwU)R|IZMh7i89%XKlRCu=z*YYVop- zI*Id){xM7U%go#PX~k;x%l1xdUVnXMCc9R&Pwak>tz3~1SI|5m{~a?i)ccyDATfBxB3n9JLIdF@)iSGv}9$CmC0^#9?-E#Um# zCiH}^pC_jWhup=r*8sqdG!`|fHxs+9e0>%6X=+r4$d7G4PmN%*AsRpQYk(aCzOj|FBjM;H8O=+XDl z)p3rg+AblToHO}hsGaqP>AbsTCm&$i{>$OO=kxQe0`vG6%)MT;JLF34+wUPq88|)v zgzm8j*{3jRYE{R#CyZ}p`_>xP_$&2D9Q@C4Wr96l>6!F@lOx=oCQT81p?S6{tNHp; zXWq+er|z27bY{(m0H5VoA}l1|vOlQ2db|BY_6mjYX#po6Ucd86UhBp5J5G_xg13Lq zI&t^bwnZPq&Rbb6IKkVpA(VH?!9N!q8tqv={my(Y&}4q(e6zj}PfGQZ%79JxEuq24 zGc|Op>NU9H|pxa(9Z2@b2Gj)1U?R#z|PRmWt1{w(&lfBU;i#p4!Gn#HM}DAW{Ygh zs@u8g+m9YGt=@OdG;2Xr>TGYFJ`Wb3U9XnL{H}JFP~Q2en`4d0I?t!`=1tAnHfL4# zajW>q@9WOIGW)(WGGChamG9FOl`X4w)r5SA{J!l)Yq_TK^q-UOGyCuq9dp_}SAChi z-d2?*nMMh@kArJXU#+U@F5MTIx7+CDwsl`@np(a5Mf(EW6OTGHzdh6O{t@>+K^>v) z-Iadx7QBrvc)jDfQ)FdD@WfkXla_~E&J1$uSMkvd;A%K{K413vwvyeQ8VC4))mB}$ zxF|AVrE~eT@L<VTahAE$u8nf|#cD7nb~+)cv2~N%ZgC#}<0C z-P+am*CfPj_L&te}*dOr)6G$uMCYV z|H~Ttxghe*y+zU-Ihv`ymzFm;#xO7~)btR%>+($@Q_H&J>Y2IEUd}%eP~);a^`Fzq z(34?j7w%m9K5%=?UFE57Lc?}U^|IhUx6aF;$^EON`nh#~j4Jit&hWdTko<1)6Rq7U z&lJ`@d39{ffg4{|ujl-+>fF;KTz>BDQGQ>OW`qE*@|CYDR|+(Vzts2_GXE@-k=^mAkGpq=cPe^*Vg5I--pjnvQ9es`!sidl zueV0tJzu}^+NRaqTuXMmala;$p?I=#Np>Da|8I!Uaf*f3IEQ`?D(UvDbF(rZt@l)|OsRT)MAyg@i%a zyeBKp-*TJ$;i}n5g$Zp11!lM9;!0L`8lM(=*{@J!qUpWuSAWKRbLqFrQ$^l#ZK7eV(iC-)br5nCPj#Orz=YmdhFYL#|)C zx~rpo-j``yS)sO<_$v;~Z>Tew-BGx*S|EDI^1mUETU_26PISBSa>KQNYxc!U_C=js zx+-YO;fw^`{NEg`s$K$&A#+u>KYZqp_Eu!mSLW$cC6DA76zzGPHtoyWExqS2KVK2I z$hnc%=J%Q6XRT(U2buUhqKXO>`W{C`Z~3%syGpW!#IFm&F0u^A3NIgPvTDDoZdSXr zccWw0((MuN7YCk{+J(crIZuyNxA=ljS0>Dcpx2wC=foU7N; zxoO?FT+}yYm-%+tt+{4lyTS@?HQ3DxICbKrlIUq>mTz;Dez$R-U}9JmJmcffq&2=p zlU6X#wRtPvJ9FpT?PcrCW+1vobAJ+sm;#S6#v1$@5!{+8z%_PtB`VYyLs~bpR4YV{M#R!9V?qs)=+yVX)l+= zAFITpo7C^TVPdIx-!gyx{@bydde%AKeBNK!Ykof4(b@3g)5kwqO|`{;6L0OCvMWA( z*I^NH+vm}NKX~_MSKpc48feszvSf<-=QT{W3!ZsQm$7a$I(f^dWP98euEP_TORjUO z<@&0>==c1JclXwP-MunYy8Ttr*Ps`!wGW?~JhhhjyJ+TB`KL`Y895%cUHr3U@lNfE zoxM*4y-Zd-iB78j8O(96vE%XmD|J&ZoIQG3Y{d&LbH4LCYzr3z_wD=Kw^rkU%12Ji z=XPI<yuw zISAgjnsnh1r{VY6r?Z->bCzwW49&O`y=&L|vbxJTQL~o>wtI0rTjSU`FRq~Q*UL3E z!CQKkIC}movA!ZSy(iD;pUCwa)>UnH&T8icU+QRORTVHO{58jqK|+4l$DUR99!gru z$nfnmU3_~^_G;DKWk~}2>m?bgIa(et%>2OfcXtNo`cpCA9wj|L!Jm`sqP;qSx&K^; zEO(Ht!mR@gE3=}4RBaZVzb%)-d^;e%P*dIcmf@AX9M3;3IwJV@t=(6)*LmS~tD@2l z=eB-~`ViFQc+7r9^X((AtVJ{DNBp~IHK9K0-ZziOKC|X6T#z@Z*Qafs!wKD%gDyn^ zJik2{{OA1pwfXiY6QAUsvh+Xx_v}g|OD`~H{SCX7Tk=(Rr<=%BQH?20Tt4aF-v0eF zS<3(5vNz27X%Z#M6BaGo=(+pnjZ4RVZ_N~Xu5f!zE6+-;?`r%7%Iw?o_bs0mz#!Zf z@Hc#&P1y<`!N%oFvo8ed-P+>asm1x2$k@C0qpS!8Vr#tIZrr%C-*PrUN?S)y# zlfd^&jt1)#DCjT*@lQ?Yl;8jR#YAD_?A{Ic*gvmyIi^2ok^VsDxJ z%jvRdbB?_3*Lrqe^uvoD$6Xi9BX@P?zHARFQ+>B--qu%A+q5+GUZqa8Sd|s3tGGO@ zrTHzNSH+*!)*n8SC;(^y3BVqEbQVM-L7z< z1-3#PHG}@13hX~EP;qWsTjg&vt=;uD^} zf3fS-MTI6C?Oz@W0`5tSEN&ZZXWenT=;7%6%5LVe!hLN*4h8O35AR3U&C`pOx%1|d z%GK*{Po0`l-x6-t5VB;MXKtpK&lLt4)d&Wrg^kLMHLf4FYzvoKm=L~@h4~#{_PP)C z9by>_dXAr;zV*mI7_~Y>qj~*Bv*}^>EVt%4INk9Qw-KHye{RntwiKC13t3yQba!|? znfrUDy%37Ew2)qcu^ z5C)s6Y-#K}4qQ}jyM0`4XRP_NGRZHdOm~e>E@qi8b`^;;%1)1<1cIHu^HBv&nrCDwMP2Xq+6|Fi{`|v zipkXdl^e3e9>(pJnWKAmDb1&JYUWr*+ zTAYS=joj+vch0xE^(Z@0Z}v=?C%HWnCq9kPW0CENkdI$l+W(_6M@({C-o1raHcYYl z>Rk4_BI3c4;Pc9p68ZoeXQG*wh<-PxUmshX_2?w>uF?Oz{|;PIZ5 z{cZZg@Z!_Lw|7t4YrycIVb+?s=##PsXE1Qy&I=Ja`k&#|?qY>I+rHgjy8ZUG{?J)f z%LT#$7!LYxc_Nf@j^h}E)NMzZo|GL|xBu>t*OI;6Wz%}4hllq+gVwFv_XBR1InLB-mRtG65?Ln<1-98~%JXnA|T*Cvis zcJ=jMyRV(vUHhoByO;Uos->lxK_M)k9l!IJxy;Y=?n!Tqq_8kG)wz?SX=(b7 z&$c_0_@=KhI_20ip|5-=Wdc(-%0S(eX!r>v5Y&W-nf`7tc^XVp?Y3@?*9)LvQs>=Dnxo z?q;lC^SgQ3naH)%rLVo*8Xgfe>2To4VKQu?Y$Adp> zsYH?b)R;UmVeyRHo}s({X(Sitw#Jx!7TvP#SJm_hkMekbv0gB8m{hy+neaV#on!M{ zKkS)c(f6>gM&$Wx54}9Oh%d#=r+0srT$OrdR`k7xRj=;Kh6ZlG6uc_n0=J`s(Uhhc zGL?e=e!a5c(#(0Hxjc8@$Me@#UVrL$s&?n!tgX4+v%m4a-lk`=F7&os-p2W%$AYyV zzv7Dtycsb+^S#v0Q*SPsP3~#_^Dj4)V_rmlz^dezu`lk}8f}VQk*Rkry}A2K|Bu=z zp=Gy&dL$lBaQwviZBp_289|?3ajF=&2{7N&DW3E8h0VrUZ;by~sXOW(d-C&wdB8Wt z^n`ocMR!eGclXMzg4b0|uEEW-&zg7erP$%^xAL-AY+Yj#S$pV_lh~t6o{t<^6FxdBan5Xh z?y3CnyNsWk*~8O9^$)57H^(v>7;l>*-@iOcr@XS{=CfS0xqEB!KAm>4cMUt`z*Xve zuP;$4z>a5P-_0w+HOfa8s7$e4@Pz&OTc4yyi4oHzl-Ct+pYYQ8ZQR>3{p050#ib#u z*L>B>47%;TW}CwVu}uLC$3&NveZS-PrX#&Q^~0S%J8LsMEliiZzdgC-_0GQoatVhAAGMS_jZz9{SRSwFP5Rt&&meSH?0K7Y8XI0q2rfMIpJDa! z+e;(w%`BO}ey;oO+3S7HzUszGi<}CTJ@%+&(WG8)r%TEcY^Ja)&G2lR^J0ZXu(z;f z$5W4UlN)MSxsUC*y2MaRoLScDqHg$yd%u?CZprIC9G`I~l)wE|_RVEGmQJ6vz*EtS zllN4Qj>zOj_I*np^&WZTTXyY{J_CpPl^@SmPyBdWJjYp)_nC3j)%o7me$&q9?#ZjY zygTctuF<6>yKa=8D(7*`F&CN;wD4#g&m+dGYOf!8%37~tX?~xQ{M0w+O;p%Lp}+5T zK7V^@t-JGohBm2R{g+nzeY#njv~ug+`=x%%Ccbi65~O<4Yj#bmrkjIT3!hC$Ldx7I z-HO~hAG&YK@3Caw@$=H^{tml?<~QFdosF04tmoO=oO5gY+Wl8&hn{-AGdReezhvQ> z(iUA-C9!h}6CBxRSackD@lW#5%qvHY+9v;X{yBfC{{9~)Z+x5ZW^3gy=`U9||KiPB zyJ+L}JLasp5xKiot!kLEGLhBG%fWxELXmxu&*YUq+?lFty*w4(?+vz7{Lyevqd;@z z&b%`>-%jV6)oSe3b}3f-&%Rksj#^q4jmN)8{J47PRBGDQ#)n%&)h6$7u)Ama;PsEm ze{-(Yy4QNW%lfVA>U>x4_BqS+4QvO3^OtU&@HODbl3nYLZ)Z{Ck&Sz}af);E$vy3_ zooqg{9PIaBZnZ+JZ~6A1w*6llbhW%Ec(4feXUaDA%kOC@HNJ6g*UJe{Z4B(AB<`5> zu9!ijvPG_e4XPC}Xm;88>oZL)m5s!FMC(KPyNH)P1nx7S-Emi*`i06vZhR%#w8upVK-xb zgZuAu`_zAIIBR>^MA>ih4t4uJ?X6Lp+@;pruZ{n0xsW4qTdgPu?~>N$1MW|K1pD`` z+AcP|DDm^%m;1ENCp}o1!(U~VeKk7f%CtCz8Aq1%@4s(o6>-PPqcLD-+7siTvrDH; z?ppshV%OgEtyirsw^(cnK4H%2X=GjF*!=DNBax4sa<>dNcyXKGmn!_aa;-&{*Mp=9 zwiXdP3RX-N9cslYtJrF?t3%D&w|RC1|8t-D{rQ)*7DtR0XRe;U#XEP? zDJkcze#bXhR3{vKdZ+yDviC=PPA+WrT6_NQ{&OX~yK)z{efhX`yWgkw1x=4RefXZp z-RQSuciN+MsqBQOmDGT$oJ3>X3m9y5~`(P;ddN0GB zZF^(4ewSPB^h#4`eP9-ocjkM;2OR%y)#e}GP&nt()#)DZd0(4u&6jf5^ekm%zU--1 zznX#P=lOXzE_1BSXMF2;t$AH#*eBPOr6GSs?afb3i1iA&VN~S4<@vOlnSZxzUDI{v zwf^$gU)M$^UtAK_ntJr@vWI&wZpoULxF}jp^rVe~2a8wnuY@MI-R1|(GrntcrX@dq z9CP}yr?qxjfq7ia{wThkG5OZZm#3G$^LAY+KK0&<=29NRmt70Grm&RHWD0Mnm=Tiw zDs@{$;R4~_KZWr&{sJ?_UOBc|u6+HU;qr8!a@KQ;9?W)?RX5hkF}l_CsrJ+p&L0Pl zb!>ZlX+fssjA;j$<=0D*Kf|-wbL$Xntk0G^0>p@KqzDjqv6}eBZYPs zluu5+m%02x<;ID7KYl)T(Bku^|QWnO*yPE)&BT&|D5ad@2$@{ecsvpY~1NPUX@+XF24$}yUnmY+FenzcHJl8 zBdk0kGBv`VpFZcGcI?58^QRYD{(MqV5uW@|oaqN=#KT?hdn(f!GQW$?z3|!pjzg}e zp6b;N>pZ5G{h7h?{Cu);^U`gX4b{vPZ~yW8{iE07!)xDpH(_2X^`g)+Y!4(Gk_-9%|iyD`&%9h?_ z@M>wQo`S|9$1@8pp0aPV>HK;5w4b(+GIyy_(aHPyUQypxNX+>8pTRPGVgGX8C3ELq z+&eLo*Pv@^pyKx>AGx9~iBv!Tv-gvdo!{Dz_c;?-+wNB1{>}5BfwkCA=gf7VGi5US z=Vl#HJf|ol(yG$p5VT;mg#Q~3yPBHEV%wA73E%x>ec_UE@07Rks~-r>4^f|>BCK`h zr}FWqlE43cH8~My5bE&FR42@E#rxu=`$NhO&z_wT`*KC*%6q$vmR!5!wPfYD^Ck@w z5_}vjk9S@aYYI4cvDE65rSj)BF-Kdu{0f6_>;3iJS@w6)Yp<57yls8Ht&A_%)qd>z z#PNPkg_T_O%+9MuPyYO8c-Gb6!X}Y!+FbpHclQ-<*0Q^I#7;$>N>@`T2sQen%O&a6 z*xawYX^TzyrSzyC`3Vb`Kb&~VpiETu(%YG_8_nkO-P{qr>dmV3Yg4QyZ3t@BD?ZIR zzhA=VRi(9i&u(j`s-}8j*AFka-W(5wYz zR%Qnl%xwp1_PFvObWyh&0Y)%a}OC(uUm~5W2oRs72FY^7*aJ&Cl``!m{R=FIO3te8b z^-^e%z26p-w^|c|9u)C(9N%WIBX>N?*lod6Z=U?Q$4|BBeK^KFcQ0$f^Vild#op)E zSLOekcl&L1d5&6ZsJP0u4<4-ATv`rM3$%@mwPr9gWX(RdaN^>t8D_zA^845Q*`sy) z!`wHQ?kMTZIIq*^wC7u1?DM=OX-$jz=5;Om9<)_srH_iml=Tl)7$)CU5uX2kNsQUO zca<}jpX?|(d9>E{_zvE!M<*HD{wbEOGnL%_?)olo*?Vs@qE>4*^?IMxO^p#^_%7m- za&V^n%FB`Kq;9NT&3k|1zq+px4`d8(dmLoU_+#MklXnGQd*Ri%6?&(a?Npn#^3RTz zEeo=)PO5Zia4@{3!1&m2hKbQ4ujP)<_g1}cjW+)-z&QW;TAtubIlo@#JzR3fM11{^ z{%-DVyS=9E6?zcm-Bmx~`SHu_{mcIrT38iKH#}x~q{uR1Ui^8h&?6SAGaB|xobxmP zljwzW{k8r9QM&hg?r(n;o~beCi)(1oq3|H)M7c#lE41Dj)L!C{UGiDJ!;bgud>@5L z<_DghKXAEEurm8bD^CDlO8>QO-}NKP@`K)pSDjtBYr@xJuHe(zS07B>FlA=AC_~V? z4@%X^#tYy5lgXHyzUOA!0RjJqk-HuldbH;5ezx-2_j|K@|8Cr7_U=-7QQ*2ezqjg& zsC*9AJG4|!hEYMJQBci#Zb_VIa?T=86~!sF@AVz(y%rukrYF$KY+CRux%TGVpyaIG z`{VWm#=W?9>vrmK@A~c%MNd7}iVz{ov`U%m8HL$yTaEnlIN1~X`MLH+i0zni%57eB zO33*u{C;J7f1OQSyG#3z(yrTaw|Q(tmTvnbws0y#(L*6ehLnnHbwYb*s_SruD6hNy z>$8Zz#Lhk!j&kM0Y^%-)}EM^S(0KKmWs{dJ=Jw(lZOY(zyAz1S(gu} zPxvS{^@;5?z5>Vl_p8=_%sZKT-B~pC<;49fm+Hp-*d7`fz4ANjsV%W{onFQ#`mT&S z;J?*tew|}eN>fcw*4=NF31_Bpx7yrJsB>DJr>k~w{wuAECI0`U*SvhYYu4HLs3{>9 zp)r>OvSw-;Eme_WW+*#2@%wet$Gv6wGvb6!RW)z;dEMz@+SRqEj~%`7`P2H){|wGk zci%7lVs|=l-x{y&yDrbkz1lX{WdEa*r3yOOpF|jyJBe zwLc&^bE)IR>$;OasjdH3FU)a4=6k#T^Qhty=_S&Klk&K-rt9jzUAyHnU+|t*EukNs zUKNs082jrB)c8Cu-*5jRv%#}*f{pR^(^GXG-|l@w501&%Mwv02SxV@ih=X) zF8KcA*e=tp3v+hs>+_$NzN)+Q^2XRBubTJfmjKZED{iMJp4=koKUxn1px zs}vQS99CMgj;mf$e{EZp`z5b+y>FtoKeTGzb#vun&!v%Hb-&oKKhjcg5tz=LInid0 ze}z@y;hd{;7#b%O-}w4viFwdUh6x2V+GqGLPrvv*)Nc8^-wSQcZ(YB=blQQ#`uAQ* zczJ9$Sjpj>n8oNFFDkNK&B^of>eDkl6*LZfVE<^k#cajJ>26OiOrEvIx<6xQ+MVyw z%sQIKSG*3p|48_)Q&S3Ol z^nB)j2J^g|C$8)%%URU+PM*Q7wkFHN#Ng1nui^g|WLyt+`f97KTEumQbIRg<3Or@+ zS1lLYy1mW*cTvQ=gvowCO#U<6zdR>2;pMeQTW{X*RW0=nx!PIKV&?eBXNITxP9>h& z$J-We+tQY8mT9!cO7{G@jYr?8&dN=n8ohUC-5*n1sR^%QSeP2mOlfkz&t`qj=HD!} z!yS7k2xMv*KH|539wlWRmNUt0wRys>OY1uMxu(m`F!K3yz+Rq9&g7J2(cU>aH69A` zw;K966@@;%yf(w+yIFvz)6QLmFBZlZaWbDXwqyT(A;#?)$Ay#9vhF7mIJx7QCmPwon{PJHfPMkh*>T};58wTcQ8M~)NCbROf`Yip%v_o;Ko;%B; z>OY*a$Lz2Bsy|{?I}l@dqsZd>0+k&ul7+K+>!z-Jc4py0E(I;l89zSvD)rs^WGQpJ zjrsVe&ubI+^0hQ}^>VIAt+}JSZ>j)?=cxt9Wy+Q8{xj@bW6$>|@SA6e(e$fl9Zs%# zF1j^c>a2dA$o%HrVQjMuLS%iGak20-ZxN{9e?KfYXv?`XJ>mi%ek%eS{HlCL%XTeWh#tiJEu zRX68#i-wp^xvIzSqIuqPxsZp4vifw!+g87px5RiFPwFYUbFQ%Qa`L%-wU54K-pC9- zZ5pbxH|G_XXVCYpl{>x>jsx0KqS2@R9e z`k1`V_V}f>;eJ)SQ(p!9T~f_8-SJY0_t4C(zd9U*wR11Fnk~L@TxWutiN!XoN{V$(gy#BAr&KnoDL~n1E zC|h;7NGtr`tQCtpov!dqiyoLnfI+9b6@}TeffQ!8n zpDFpe-degn^(%Y+J9Zs$+vPhi*DhLZ^7YEmrB>5o9gPAWGFVJ}x#oP{F^7Qb=gseD zZS9@(musVaMY2M5#iw){;!I+zUGGg@*rI#h%cqk|Wrg5; zrVx(5COm%y*kLf`#@&b< z&z4UfjPXY7bEUTLv6ufIthaBgdxf&PW$LH1F3VPay8cM$k&_f_jMMV21_PFIpPqyi z!OyGT^L2U3yJWFhO)C5m8tP#CU}wJ6w4^2B(Sc#hnKGR$HM2M!HHA3-7Ff$~WcF8j zFlo!)U24l@{xc-cKelO(r@_Tdy`{!m);=%E@12~quxQy!_of6&mT-*23>dSimu%>cSGBh!@k>^ z8a#a@H0K>@KH>0V^6E1keLH`zv8=o*l%5+kZ+UI$49`ptuGWte8$7==3NiNmXUO?+ z|ii-LUClrYXfLqmnS#Xi2fjLI)M z*e$r46@Aip#T%=T<^JZ+p1gee?YZ~fms9p`E-bOjx^U^%AK~XcJ07zoa?4ceD{4)0 zk##K2SJDtr(7rBfTkP{A_hFK?W8&r>63l|+cLJrLQx5#?(g1hqXb|{}IT0Dj0hG5M0e~Q(q zUHd=89@)O`s;0=h0HvvlaWl2QtEN0CY7taF#xRw)czxf4-bdckuAJI4TT=P9(ysC^ z@6y_mjw{R;44MC;e9i62%l?Khcqz-PcegzCoz>Qrud1f%?&`8y$KL&R^>pFW9jq&4 zI%YnZxb~m?+^w2Y1yx^v2KV=_xt5ao<*|I!yHM{Fg^EXa1aYS-v`$)DH`$^@yTB6N!WIV9m=fbC)A&YbiXQITg$lc<^6zf`-9(It6sXd?ECH5jYk)( z+IUe$I%DR)f=GWMl_i~Tmx>!5xA7OeQ+o2?vKq~N-&!|eKn9$L{=t7o_``^fFSq**&Ms`8^opIT|u!x_Ct1SS=pUSaoB;%E5f zmo=LlCbGPhmVWYRs_D1Am$T}NOTL{9-M%_^t7zMt>s5A>R!umsc(z^MJJ00S-lHBt zj$urbI4-_7-I2T|yYv-bp>y=iu9pg|uluGbdnWFR(k=V7YTB!tTg9h(-7UHqnW(R- z=&E7J6~H2-BEa+FXWpb#Ar;sD%g<+JAAF;GVMF3#nLV#EZhvF_cHe1tcJ!LOX%UsB z>(?%kidz0tW$TZE8PS=VPej5E)~QI$70fQZaZK>BY^{51W}%7d#>R=yeNi~hF9~b3r-k+zj z+*xD!pW3Jk3pPm|3{MF2<71S&@&5PADIcR-|1<0ep7?NItt;zc9qv=Hm0TP4THe`R z_O@tK_`9d;uKYEck`+Hy?2tg8b5nc4N|E-_fnII!WvwT)XH3%FB0 zKcDmI#NStXx4l;j&W-7}xxDk6-=1q;WUv zu1=rJ*L8K)?D$nNRcEoPYEr-?9(To#OS>;0*|CdhQ)b+PPo``3q{#{K6ediLoAOQX z@}1jZN4MqZT)lWT@WN$NpTz-_Vv6Q2VJTuYeBWU#;4}GKWu8Uv23IR1GmCSNkDuCk zTwvCAxf&lAnJlIpC}{d2F$Zr=R${`}XW&$pMC@~#@tGWy3-8c) z{=l;OKf~FyXNh8GjC{l8_LeqE^Bs9@_Qkg3&eu~q7cXsD=N3;!9q^0KFDK6?A>%@6NHjezgYt2q*P+Mmwk3gOe5)t~S^xI4p{^|rFdM;*8epQ*VJ|IW8-P51c8a@?O+{%ujxySdq3e_v-W-Dw?kHPt%%yL{H< zsXU&XZ-s203$N!DUby2J$H&`WSN5KXUFKK(GUngDH@~dQUaj3e!Jcc&lBV_fcCnY{ z{M#>QU>dUbVJ1s$7yHSdq06kFY2T{VI{2n`zQ-&PMgGu&7@cjmm)eAIp71H3BO%rG zpP}XMzDD5<#|r_{DwNLfo@l_5|$LI+px z3Ek6XZ#?VB=VM1Qdi0AkMW ze8JwMenQJ$d&MwnupVJzZ1O+mpa1>pG|wd8a0T9or*l{4p49l|vF6*f3ul9E4(BR& zP2h3#U@EBpT@qrpX|dMJJ3or$|Lo6_JY8fVH#;~xHBzed)Y^klAzRcUG9 z{5=2chr;Xom)|k7T<3daxe5=@=dUcuAJ!Fq?EN;QN%Q<(J*#i6mX}xGb#~NrHDC0( zHJvk4f7{Zcp2njr#}E9NP}jD-AnC~Zxi)+D6jfc9d?aRdY^lV@kKfj>+Piv7-f36Y z)4937_XfzHs{CaZ8{y~3n8oIzz5SfwV%_y>p;`@$4|~rq`(oI{8W3qzS8vs{{LXH@ zwhz5)9fKV`3;mfgHE*?S^Go>XFS1JIMr_3@Z{3yr@sQZ zrfs_9JZJBc1;UdiYcYAb*YEt#V17Tc@v@t{@deOomT6z+JfDBz+N{_F)kHqkqb!Fm zKlGZ+Y_WVRwiyVb=bFIm)!2V(tCq%hrCz&>T|*M>8u>Bl@n_FKOTJ6cvQ<{ zrU%Qvm<#qR{xk6FoXS@`x3Fi`CuQ4O%d4v&>@yZo-!vub04({y6Etaf@uZmgR?Sa1u^KD#>6rt1~AuUBo2 z77n=Uyjjt~OWF3?6afy-p7{bSFM>t3o4xt?AmhX9x6a-xgkpDA@P#T(4Olz*WyrN_ zYdtv&j3(SH_@-!cqNCA3eOpb&4vT2J=@Z^r>D~A$xu#Px*l*(N$MGyNucmCz-4;E2 z%H^L^!+)MHa#2V)^Yi_k7ttq{yygEc{_FIGf#v> zC%9J(>&O5 zrl;n7-vPPLh5LFs4r|=`eExIQ^YC!R87bFJFI5WIa;noaNa*I2d?3|BmBwT200Bg7C2B0J{AYwh0{adsluM3|a!r6SMvRR5LLs~I}skZF<%K6SG8}6*Eyz*||4XgRvwaoTC zSQ;8?bYZE_KQgU?ja6%B0@ zCWI`Ne8lmHtK;g$d~eOEm(>p+KJa***4o1rp1UV#&E0G7e{Ro?ulcXoSJ)=qeZ4PF zoA1W$+|Vl)tE^o$^mg=TiOATk;44&gP_5vS=LozkC=@K^i_*UP`lij?vi4XW&-#liRXjM=x!)V4$X^bvekaYNBFR>G zqPPEY)<*xjHXo zJ32OJbG44fs+zCiXuaT{3ycg1pCb?1~&d z9DnH@dgq#ez^bEdA31zh1sNOuJ>TmyZOL4Rz|N9Ydz*WjefQ4axvg8e?8D5C&of15Sl(vSK4y5A}{zcbmu;{4~e zTnpRZPnUl8a?PI5D|udQS7D7|SNgnCk1|QtF}6Cg z99NlmL%WYT^_b|}c|Jx0cP}sb{OLx&p!bdz^RgEXmi5bDyVUCcnsno~d-ayHZa3~u zUpIk!a`t})-kq^wU!6_jHU95FFs}o1_{r@f8W|e}|uF3Y=r>#fvOt+uB3dGSlW+Vf}qXAqjY)N77Y8)ulh)zkDxr`vW4`A)T&>h;*- z^XphG`Np+Tikg$3?u~lCOn)6Wdz5+U^QF6I_07&L4QlHQ4-L5RYkknwfYz3(&Vo}> z#aFaEGt>M_CePUZLC@~<)2DG~(&ZF>Uv+;J-#+O^(~|s$J^!xeZ?}6^bm5eiZ0WY# zqpGeSZEl7<;PGmF+b@-S&gT1dE5ZjVI2nisvbuvGft2uH@GL43qkP*fkc3t+7dd zI8%9xlyKn3*KsNJ9rLf*{%2Ubd*9DZYfD3vm!Gq^%pu=Z*%~tO-GkU%N5w5uiylu{ zOAuSwXO^kEMOCv$e6y5XX zrVMO0LD|}T*Y0HPoSgGGFy8*z)ipNq+oqh>Jd(X%Z~ysY>z-f9`?dA2$rrudFJCVg z3wtZFW&Ot7ul?(H0&x1UxW-?*>k)%v*{ccq_~$tyV?GL>IEuDa*9}_H&qE__EnQZNJ#^Hxu56acBKa-n+Fb>GC$; zEqT_frp;Yd6%w+mp6{avr=GYF)1^Y2iZk>6`Rn~y+^D*x!MSJuJ%vf9Ki~NC@;+;e zhqrY?@a>oPUstD2vvcS;UfW#*ZVw~rm-CtZ5-q+jl(yv?(orkVu- z8ov`2zs>iW61LegNX6mje8ERHC$HU6i`Z)HG3&&uCF`#z2i%F8o>bVi=^In*SD_Bx z?~hzGEB-Sa{=3-Gu=0uR8ODbb4&NzWWAlrFeX3s!=MvrUHFmFrR?cYhIWSqtJ^$;a zvUwWom@}`i8!)guKl0gB`}nPeqP8BNx2tEW9@*C9JR#6>j-N~AxmhuPZ2vQqZRa`S zR)1WjHZ$mBtl`ub8q4Ntryem{c46zM2Qws|O1@V(DQ8z_yxRBZ{98=Ng?N52)oXce z?%xxwwq17D<=ZcXwtM&Oy2Z%5pr|nYjs5=m?eA^hT)g3=lJl)Ohj;et$Y{@Xsh5{C zHJhz_z4N|)bW`7=dFwj2>2}H6_P7Ag@0^E%Y?>VGYqA&D=pKAJV@;u1sO7x0ReS3GNw6)CVfNRanAA3PmX^xe zZPFfI!85$vGkVT9RiF6#_3jRRuD+Y0i81NNlfSNWKI&8C5!p5?I)259btR!&MBl92 z)VGi;Gb2*VlZ(Zz`g{MK?^j>l{B3r6y}n#%#ExT}X;-Y0b3*g)Y+w8M(%jt@b@NVq z`*VAX&)QRQrkY8s=1!>B-nWu7!ol!_zzX(TKC)43mkY}(@NT#6U+{6s^*>G-4^LRc z>(xD*Xsf^F&a2#2uf9Fld*OPN+}FI$Wm{EC^F=iiR$r}VjB(%G-l!Q)9gv*WMj2`;O=nmFDbVV!Se za=g7@s^)G^{!UAIvwJh|_*pBs`LF+Ma{R@0WPDoVW}|6Hqb!;8Y~+(p07P8D6Mny&uozz6p^ zX(p__ud@udt9^O-+MVGBx8SXf;g>I6-yU?k$2j27r7arP?;JdRG_~0rJz0KyoXGG> z^GWKJ!wI)#dfxt!n)#$uzB)fRM)cSG7w7K3Q2P~XY!ddWc5&R+uv?riA_~R-vui zW^E1*X@0d(LvMKp=ZQ&9K8c5Oaf9(cHOPwm9wJS+X!u<g17 z32m+3*-&t2G80SlZ50MfhwB#t-8qj4zBzklp8J1>nCk|+dlLihyfAv{u}eK}^^(+C zEh`GXTnp+i{BxPf|LHsb;Im`FDXswK{t3-IOoRS+V8)A?DBS z-nb@JvPAcHt5?v16*dBG%dZ4YousT~#Gbt3?HqSS1HHDKclG?wuBwCypDS@Ft(f(R z|Lgp(j(h4hR9%hUmGkz>?QfxL+4}Qt-S=OzBv4K@l%Ma?PmThGe*shaS?21kv(`?S zuc@9RA^ng`eBCP?+E*t~Ri+_L^_$Cm5wPIqN$&U6mha%R=y%h4x2r>Ji5 zWZp`dcMnwYtaL~>hF8q%6E$RcE=DbwfjIU5RhAO$#&)*ty>g?94x~~6=PazRzsC-s=_Z_0Y?Kr|7Q{wMn-J2&nxd;X}GDO!2DTp(5=FR#hc@j zX3q^;cl*jU?$V9BqO4n$Ivz~fdcq=&L9<6oMfj1a^TRzB$4ibG|2_K5@nzK68VRFQ z_F7CkOU}*DzGzrdbp6NYkabFPc|K_5aUN=v-CcCN@p;&9Kl5CpNA8EY;u<8a=eK3Q zTfB5j^uCGRrTdNsCi0$Yp6dLwPi6TT#yc%@I?pw1d-T~^r=R&-_v47{%90m3`qLQy z7<%%4yj1!op(W(r?7D@Hjyx@srn=SZ`6cgoFSY+x;YvM*6EWs7{g-osJl`yB?JJj) zZhU7PZof4Ci1Dk7`{o9g9DAI9b8r65Yg5e5ivRIVbIm<;=a7R(?yL=4?Ai`WOx0rS zJHV^;qq+HppWRvOspoa_`+Z7m%NM+PubVL8q%>>2&dcv-OY;}VN7eg>hOJt+Fh*Cp z{iXk^uqna23$|ZTEziz-@ujh=%4*rAIHxO>0;dlu6su^Qyl%4pN2%@;`NS!knRlpj zu`jdy^YZI@NnPQ?rCg`vSACY7V)-@yPj>l$m z;$N~yqFxp6wi}8zQ^H?q8!-09>+HzgdM&u)82`WJd;67thx*-abI*B|R%Co4rEcos zyro}n7nRmM*){iAZm6iP$+XOnu7Z{gOsB3aSyC1>%~O4Uf&#DggZ;b(|DtwkRNQc@c7gf94U0S&T6!3*ZdHqIQ+oHbja}8^PDO6a zqeH<ioVRd@=NNaWpDHIqIJb%&6zuQyqkU5D`!X8>p5qQy#ti*Y;Ex9 zOI1=}T(w>P(cg~WuFHhOZks(`#<2KJ!rK{bxeg^CmzrI9@%QxVz3I!#)-9OLx>9@h zs+G?!ZOp5zne_MI@erej8H?sFyUeJtVnS+Q@TAY3_XP7cKD0mibMl)qlYhdwX;E+c zW=bz_P5ZU-wcPFd$L@dmxnc(nCDV^s%^#} z=i=f_8Mh-${)Vl87(~<=9==vCn0 z;Fh&k*WvuErHUeX3#O=M7z9Nt6nZWAUBe;Uw$uFB9p1AyqKy~2EBM{~Q&*btBfryR z+K;lU^L8Awf3}Nx!><0#=Da(1brrqcw}NHqxBI7V1%|lmoth)~pTRdob=zhZ%_%8) zH?7kT1a7+NEqQ&;&nsWw1ca%7>7JSY(KKt(Mnv~-SF7-brnaryW&K0;S7*t~}ua%(A^2F1nY?s*Dl^#)(Ez6;LCcm6ArPH!M&)D*>$uCQVZ2{ArSw0=m{c=`! z$HQ=O_O1QzE-o%9o!Xtqv8Hg_8M7&Ehxi3=-@h~Yh=4jnrps;j-FJ!N!SQ zmx?Z2Hh*$;%jMjq9*Qn1pES7?669IRKF^;hnkRXP<59Xy$LmRjZygG)uTPUTO!)C^ z^M$LML>{VoIi;#}TFy{cv+-m~mR;`m_8og>i)4@RiU+@IGrX%4WgJfTTxrhP95U66 zE6k6vflZ-V#jAZzxk63meJ+pcf&<^(^_gEgesth^>od2}O?OIp)s=bj9l9+azpr%R zd@_HcmhGMyAJ;GX&#){;P`N9*qfO?=oPQGQY(IvcV!pBSX|(B8)zm9Hwk%O$@OdKh z>3hWoHDeY3;~^f=5w4d1YL1^{wESxAl8`PJ&3vwe#is;j2DLOAq%hC; z@JhPYX#3scCocKv<{h$sn|E*i^r-MJ#rZL2`>yY{jOuRx;gIXE*eaaXDz4!drDc9KemVAK)gjg2mQyCLs0`a0?m6R*=aM_pZePx}-Hy4i(nRmFctrjL zttS&WG~fB~c+5Dr=GvSJ7n73$YaCO47%iE}KGn>sa_dAKS<0>r0u1oMf%%2}^j?H7mu+)?l9u2bcfu6B1{hf3@n9PA-^nUwMA}_PJRm zZHzxdi+;^~aaTOzS7PPH?H(`hd~yHdc5PXRRMfm2`KQ13q%vOlZK={EdZ3fjYE6yv zhnqXLX+-b8G_Sx;FL#|w_L8m#UTeawR6|2K0SVcbPfy{F=N#$OLX}W!GfBrK(+Oy4Y4ohr*?D0$M?tl1`)p~CK zu5HeX*6z;kog~KQ$mBTj$Ye>|$4BO-H=K$pFRM*IJX3BiFZ)N)Jq1o8b3VU}z3sre zHbHU2Ca12@x7@!~gavPJpZM7K@r6^SefyuDTdCOXzVX)nu*cc?U2?%0dMs!BWsa*> zsHty1r&Myst7~p|=Ax~06}UL=%~oozyd|2uqV$&f5{t9WN+BmdxYxxovLEU7KKx^{ ztKH=DE04O@=bSU1`)cQ%U4i>`&xKsMd@)kL;G{;WDPud6y3o`~j1AQi6PI23xY~Wf z7N?1Q@`cTJ&hLNfYHP4HFI)EAx2wPNLT&&dfoG#Z?#q9UVBfu{^;b_Nn3tp*}W0!7gCnvd6_TVaMHr+ z1H0GOsPurVp|@W>eie7{%lS*E_kG_Ta>=@?G^y>pPxX~B3w{BiqJT+US8P<(6*5aN zR(yN+XiW`!^XW=smuBtx{!1(F-Cq;4>-LpdYp+(^tN$4Hb>CH~+1+1Hg_=Kz)6#tK z)q6tIr6B26^`E)QCmy*?jVoknlJzpGVqL#xhy3Ay=-W#I8&^wDGW))E-t?{h8?D-R zZtHbeuqW<6!)&i$zu*3wF0TyWlMm2Xc%tK3tA^lDPqvcjJ*rplc0Fd6w0LD->{!@$ zN<2i%$aqed-QBX+Oa0z#yJjDvJ;U4JluVwpw-!U8GNs&ZjdkO*<8^SeVOFUZISw zL}!laG=&Qfw?CicE0q_f*x|bc7IT>5v& zZb_HwOS!15oRXZo{ny29*8=X!JS{Cfas1R&^S4_)Q+L<~=It~saEcAqDAkB^wK@^N zFj3Lvhq7-@b&zP6h~@1cnY>?TY@2M=)~}WLCaX2q>-N@T`=*w5ma2LL2KeZ)Z;d@8 zST)@bKa7PhAf4W9oQ49>7- zytcgh_`2MsQl<8t+u9zy@}K_N?{t>ao3E>Lk7=##D-62yLS+RLpG~up!^GR~muhpq z<-axEmH--_Gv`_?WOIkX{nw(k8a-q&3NEq??&SJhR_)!=;MVfmBck9)5`o@$tO z)_ezht4XFAmfLn6uDkU+?b*$RS;wxu&OO#7Q@CtzdS+kk%TJRnTmF4!b~vwkZBBwt z`;jYc!q%K7NvnAeFRuP}v}{+*HrMV_t(jZZujpP_QRNxp6_TLFIY+DJ@wr(MH37N1 zx2u+}k4ygf<5Hjc;iM}m6`!ht%i6QI`+KHuHgydQ4|^qYb7iT~_Gt`c&HY*1+R z-~Co_b?5#!SGKHCIUndK@S-s|&A#HwUcH;^mh8Bxl{@v+?Zl~7E7oxpES-8#)l=hp zvsQ!q-x3p>(n)Il63*vsUgyb|=}B)cIafLBwfn`oYcHPNd!c)K^|ZGSvo?RdHtTo5 zM^Y`7&^;j9}I&a?YOQwy-Wy|(DIx0H%upCtQbV8=Cgk5BBc>8a&AMcAT z0^gsxGRL@uRdun0@XBX3`?gm8ntSzZ@0XpIZeRERW_rIg|MjD}%d$RKttxdDukOqd z5$k{IYE&t~$#Pe&(;B<1f`; zmap6&6#aA8@%+55@FuNt)0ZX>w_Mn?iG4YvB43E0BBMf+yQ+-m!lkd{V?47?9a5<5 z-T0qjt!>v=-Q3CBBq#0lFWJ3pw)e7XqqCd7)y@@r&B|(#HFryAOv=imYYqmty1IH< z+O$kEP2IV9dowquy4}q0rhd^k)25u&zi0nJYkh4}iPP%x$lLqhzTG$Lb#rQbx5*l> zQx?m+PSqQBC|UPR?hUO|dDy$a$c?+_=iEQVjbB&0vp;IGG+&n(y(W6z-M~Q4Xs^HP z_RYVteC_f|?yn*Xk2jt2U`d!fQGVUN(jQw+%7uAsoL6!G@(=B;5g$9|8F(&>yMDiV zc82OH)ortTt)>V?uhtG<$W}l7-RIjIe|}s#pZ||{Zp!@hnZ}AwHsybk)tIMxxb+#6 znbv`u-|yYo_fO{XuPIxzb3UX#vN!lH()^_7kB97*Tc1Sco;s|kWhFR&ZMby1XWnAv z-H#u;Ph(|Y&mR`{E91+xDI2CVC-{qwh`{cBfBLj!fisKdM|NhA`?U41ic=tT@xPGDcng0y81^K4LM&^n>Q7}F> z(=({TjYIo*=9|L%8)sZi??0*XgsFP*wCDMSbAtErW`sPSxN^7M9bfIlX^gH`3s`tQ zPAF#a6sXVp(W}#UXM;dz_1x&%eF?#yR~zqFsinV%PXUM zqYGE-o;)6KnrmHJZw8l_cW0@ijmil@tw05jj)_+8ANfVAIfWj3#K@%AyuXl8`a0<5 zhbNcLsuh`^c#)OVbU)~6^yC-+1SV`P@@JXS;xfd<=;EF!w>A&*M@1P?843*>et=`OMso_^- ze3rv+^=wb}Bhe6zwwdcMZp>bo)HIP#bIXL=3KBLB@4wqVQ&xHX;va9uwGR&~>ut=| zxz`$&oMpMg;OwfP={ln7_@*2AdhyAO%xs+7aFf0gu_ray}Q;AcIctZH{{hyzE>8;(`yJ}-@-GU1x zuM6&2CGOFhnzdv}rC*fxv`MqdGp?=kE@G}eJU=J2eV~lH;YViUQqG&%TmoU zZ&z&LUK2J*5ZiOKFQB4WNR?lc%tB=8qaGV3T~I#zmhdz_~Ywa zSTpx&`csc}6Tiy+ExNP2J?(mU*gGq)D6?#-RT`|v4hFg}dUfFYDu)2A6U{#xv{>VB z+jud{_A562G1+vulPzgY@x!p(?Lt#m9&DX|s=#*hOgsPj?Z02l2v}n+Q~964UMtmGA^>|gP(%aT^k;kWczzDmaH zK+r)ifBp;w_rCuO`B&!F8p{iHvt3r)_|LUb{YJRCkno%*%?~{O7&=E!I`MRg$%I?t z9`Dry9(gtxc}$)tEIG4z4sZC=ADu}PZX77t_@5zQ@#4L@3JgUr_g`Kbv3~*A*Py?y z0ZXq%IA!U+Yd$ToDJ4bj(u~Ji?E83LT|YQ;Vcf;*Wv>kTZIX|loAxKLOs#Y3bl%I; z>ZaabKIxWhP&oeD5bscC=g6JmCh?dlp8s~4<6o(Ag_1Z5hSPtNJPeBVkgyuxk5 zuj38Mc3SNA#`PJKdzaERV=4b0>&GY}a{A22>vOonHLtqaJE`r# zq@A~x>BL&@EID7ez9Qdx@9xsAMVv{S_Le>L`zySJWob=gQ>%m5g5pnp$xiY2woGX4 zEPUDMdb{AKxRu}6L_LPhC+4XhSmZRRW=Rs`gaY>b$qS~xdwBlywK+#T3)Y_7bERG) z`rV_uzgMS5NID-onjhS1)35SR(WB|np<+gbk7|xjdnTC7@3`N0`OPoK^E#sT2IivI z>z9YS-PyF`%GoE`{pDKjWuXgiuif`OJR(cuTKhB!ACr{Do!n16EEPHoYO=Pc7upET zNb%LVmvBAF{LDk2H6cHrzhHkp%lpf(wyo9E?iNPhiJbH(OjX;*N8;`L{@#e_?xlP5 zI(yUC8GGvQX#DNK_vEL!{oL49;(FPOb2Yg0mfm#k;NvqeV(~nv#{akUoa(Nb6DQ7A zu6bJgRcY-X-N(mN9oYT3er&!Sxq5Yw)?}B{y_zyHTvJ!9mQd+AR{h-LNMVDiaq{}= zuZwv1XsRVL^h-_N6KuFeKV;2z&!|=Ak^JkHddXzoYQ7^X7gz6hQ>nyvIghaOZB9-* zcFS)odS2h0U=V&>;_nOP6}wN}PTsCNcW2?2YvDVXELJ|69<8Usa)|Niud+8bnfI9= zbiHqiP5vxl`Q>P6#oS*BD(r!Fi$#JjX8X<+c^j)W+m|(9Y4&WMm0o#E)4Zl_SpP~j zvV)1uMEz*Kl6l(u1$^%FmqzX|QB}TheE;+Pr@G%ohi$vJT--nCZQT8pE3_(GbyZXr zIvlz!q2&2!;rF+z4t+d*tmANzw7c8EpIzZccL=}IJ1!UfK1$bo!&T3Mg-e`*%b2f~ z=rB|=Kk+WY7_OTi6xP2wx)9w_1N%6wfz2|0@@>~%K-F9g{ug#RD zsxlpFK|&Ud-x)ZMt^Ra-PoG01|i*&aHeHZ+$u|XUV0&wb!nP$81@dRhaG1 zb|pdNxTdGa{PXuG*e(t(c+K6cEa7>{@@-?V>2J3iyxU{jpJ_*@2j%_}-+wqSls6=3 z<@zVvZZ8lERTA6MIpsO?fu2{|t5tT}wwcWT%71S0ysr|iZYONM9kYEo`AW9>k9(Jw zuD!W++pN_O!iD;rFMIJ>oD550wtX`3#*?WZewj|a*LicURRYgnR+UE|tem3m=l@(0 zyYWARQ0}w0rPs5ptds6%Uk(abqCG>2-{ct|o8P+akIrRsxs>QT9ajG$QqQ($l0^M3 z^A8^v+g{$fw&Y6Am4HBttKkZdPh@@&eAjoN^1br8(o>;4hnJTA4m~xutL^x@b`dYx*1Eie2TUA4CnWD%?VFv=+!oHb{lqc%eU;Z+ zr>qsLvAn4FtvGw>ZNJ;D+S^_qUaPw+YnilH(375`fH#hBRD3EvU2eH`%&thOm<-P$8~ zg17&2R%ERVo0#{yeZ0Q{?)|tU_BwavY3JExD@79>ms(7_DB#|-G+FYD|M|JH*{LGu zLYvucyq)uD6-zM3jgAy$S z`JwWsxA>iM#-(FNzcSUB**%{tm%DbJPR7B{$Df9aFWw&(J^$sTTV;87)@=^B`Mz6o z$?{dHd5gL>%&|L^{&~VW8$QvuM|!Ia7{zMDqPeps>Y_g(h6rQ2U# zeyte&Y}dAf*I3@#PIzth{-c@R*HEo(mt&;4IT?MghDL5g$I}2U0(L(d-2Wfeb?50wP!EbewBS@#X?uD z2OI6Li1s)NN?ewdJk2S3EP5JW@zX0jLFt{3CS?1ht+;>XxlindcV4xBT|K0?URO2Q z`pV12=)7KZ;ek|87j2Wc5kn=k>sE zttU-QOmu_HPiV!cDl{!xC_HBlzn#ykvjvKMMh@KbW?IVI?khbjtQPmde)^e!_W}Es z?VGzx+i!c-{Ht% zpN3`R_H-|G^Pg(EtMDq{{IB^L7vIaxdv{yy)2;QNoU&d_Rn&HTv}KjQ+O^4jTnv5g zQlYh}N@9Jb2V+kt^xM8T@x<)qOtY_xuD`kx_E6LN$(jW-nhYG2l?Ci$-OVKgXI#$x z^FmT-vc-%iVIeEl#l}o!S!TOT&H6!qPQ}MxnHzTN>lM!VbWHoQj`!aa-x8|un<6iOfN6FHlNi5}oLjDJPUayL-_s`8=8@fJZnsv{-#iw?j7yYs1 zsD9e8e{IgMZ^CO`g7+>dOr0PgUCc zW9o8t&tn&)$)^|6EH}XJ8a_rt`ifRo+aFYRXfd@jO4@V4`%pwdlm1Uoz>h zx9Uxu`|sg7yQu=NuD{atYqt$KIp2uKr>42*N3X^99VexemYo(>Bl9aBsrz^$W_M zEN%aG>{s2=BO=YdVW&M}y(?5TR(Ww|%rD=uFs^Xfx9@=_JqMj+H(seN_B_uuUE;P} z%lu1SzoPa|-!^~6%@-T)Y^(cJ(lu*V^~71-T@8~yX{q-*xg6j;R(dTHX~a zLc|{Yp0tFq?~~_y+vRUcRo6U_W%B#pc;HX=Aw%iM0ryW_t^T#}WvO|XmFtc*CziQ} zKI^rAG;N9I3a#2Dnyw6dM>Dh?<(C&TT=}QAd6vhPDXf0azuD{yx&Dy*%eH+?!L2sm z*GUCkx}EuQ4zFq2f|cbzd$&ys3R2Ye#LBl4pMJEMmrX)a#?PtW7vOf=cV?5gW^-2u3ZVblaaA@ zYjUe`;)}{Hhk_MS?d7NV{5hWYtxrB%``ATKh4XHg=T!Av(abOK&)~a%E~NU)g~!## zuk4dVm-5!>9``J?KOHWra$rKGAZz6Dss9{Zbp1x*1U2L(5toDdO0rh+wbyIrgl?88iQYGE}1fE>f%Wr z0St2g8C1SsoS_KBoTnjO5fdr&k3w*DZcsyIy3*+3{TFyn?pVfve8HvyMGIVE43WMdpfcwh6x4)TI&1UZT)90@BOYg$zZ($Q- zQY(L4&9FARn!Mv~-nH&p-D#a!2A5aQ;GM*E=&Jpc4wcHEOeHDXSDGZDGBdV%bNB!G+Tegw&l`=Q~R`}+S4+yxby zncv zl}QE7FHg1#Fc{zd{qpSIsdI!)4D|NeoGbZf8ad5{VY-#CvDxA3@Z!tX+u!%a>s+@A zV=R0os`~s1^9eULAC9@&kA%9N^4_)`zc;fWV|n7eUl;72zxe$-ZtbaGxo5+^X0LvB zxWC?%OYkYnO!X-Y)fs-WA9Ig9aW$&XUT3B@^KR5Bk;M=C=f8BhEuVThEIPLCZ(w=u zmCHI?oNc<_o%kjycvMMU-GINMS6ub&{=DRda{eE4Pkg_%b@MhBmK%Xfx95jyPLter zb;n=j*LIVf*GYOAB-kKb{rq^PYfuDBStWDdd zeGd<=PI@85q4UwZm3P;I$w#dCGgKr6-180xM918SVsq56+R0P&ea#)o6kj)6i_d4( zHOkrE?T@KHtS9nr|K>}d`$DfocX?!L22Q)SGhp)D#~>0YV9le^m0qt@7-2wm#t}CvPrC+Yfc19=j7HyOUnN>~|;qv-iekMQ<`rTAs^zB;3rS_4xitjt4xRPP`*2``Bkk zNz+Xx2Zy(oS@%U$wz(=_GAg_>>-o}O^Nz<~J)LSBTN{-l`d%@kbaQL9pOE!RmGdX1 zUYVJP8-DnnDbpD|QArySM$^S<@=@tT)b5tczfN= zRRK}C&t9IIlX|$%=E8!c>Jtag7vI{jUpVI5$-F)Z^NxdZmZr^Vx7!`gKHmRumg2&7 z(O0idPoKR#AUMD)Q<__G%g5uUtS@82n1$Wrhv zY7ui)cQ`4N$>uKHH zC#~6d>R!ek{3sW)=~{U2f!-o_d&cN!;dOg!U#i`>E5_HEU$-nO^IpRBJ8Q3T9lvmy zXGKA%^9rG9Cs{ea*6!>K+nai{+M=Q5TlVD1=kLhmx|y?^7tLgP^^@`KywcafFDIwJ zliq&G+}&(>*!QYmb9cP@cGh@0_g3e3R=W;u`RuhfbSZbFLlFClEv*_$WX{cr{qTOP zn@jJ?__|7kN*=DK;XKJZOL`03Pd|@&@%nOpNx^;H=sD|Kjf6C(wM<&I?8-U~4G*>u zPbM}8_BW-Uw~PEx`nzt{t@~ap|1&5R_QzGMe$TM~+%%i>j{CGGzyHs$IjtaDem%npm^qSevMf`WWB>M|cG2)Qk+e>wNdC6%&tq4nnKyWek(w@5uR z?K@L?%!;SaV|L$|ANf1z@Rc+@(Y+???@hCh%(vLJ>UzYA=#_Gd!lrBzWlEmXtmt*B z^s36g=JU2Y!&i+`qYceUvY2u)flth z*~Kxt3NJG>`pp)dcwN&q;iTHygMW^DswAvp+ttqUZ|2SD1IwO@-YM0x=b8JukNd0V z%`3cni(~$l7r)b@4r?I{e4TZvC(27tZa=>E>Nr7`J*??#}fk+ist2Jnp%CP2tzJi6K*F zc&v0cnecqel#cU>$Lv@BXW%$ko^niZk?ZU8$}6mkKE0@Yxh5vg<9M2g`OJHJ51IR( zne;hVlOxc}y~gvi<QnHZ`$26pDX#z-8rjIzquZ6CYb59 zXU=|M2Eo4;LhcpkkL_YV@SlNW=JU7f7(!(^R`nl|sTH!-*z@P*pWWR&ttRcaoF<>N z37BN<{^jSS8i(_ZR`0)+&X=T z6Bq7Ap9+Wy`Q+52p_y(|+<05a&+gU?het|}B}JQy>dwi0HZl72Vag6kFM&xLmd;(!2a}#gZld0=0+FB-`&>zGcVUZk2Ye8n>^zJ)Uy)_Gavu zH0f2)mQP^;t#f#OFw}4PesSZWUnN@egT3z<%YI++b)R(k^iq|=z@P>eewMx?}ejapS=V7BaRv{DjRCNnZs`<9+ z@K@dX+f$@1qT{%VOG0eU|45Q5sMB2Dbiw)T3W4ay3alIk$?xPMqgt6&_a3v``O>qZ zTT4)ZA^p(8wR;P8zUBH}w(s+%xcAees!Bo@J$9S6GjzimRoA5=hmI7+%RS-Y3tX$0 zv-nDikVfG1o!=Jd)+vQpNeDmpmz{p)?$Z4ct5@8fF4`JwGwoI8{7j#ai#q-JD=uB) zQuE|G<&!WsQRmUrx^Mqv`d@xstovie?lTv7Ry^PT)@Mo6R>fnpRAcsbYS}$GpLwQ# zf?x59BO9_+n!+WHzjyT$onmILKDY8e!>`%5)R)y}+?%b|ckzccccXZ8@yd9Cx56*= zT9yT|ER4CWu&w|b?ooPICtG9hQh`__g0_HJ+!jD zxzsE$V4cE}H9J=LcPKdi{Oft$)>G$B)-H$sD+%-0%1zRyI zj~+X&!zyHV#8dM4k=HNO9-QzxS#n0U#(?wNx= zx5nl9`wst#UiU*26?gG`5{vyccdwz#ji%+^W{0+aGkYA`ymGdA(a!%2O0ymu?ptdS z{*2Xy|5N|jd$pTN?mcepF}YQGe4hLIWoBPjt_#UX>??g4HEp43S#@nep}*p@OTkBu z8#4D5CH&jxXPtT4aYpmRCG}e>ioUG#IkI?b-O}~3!tYdC&g>N8RQR{7!20ol8CRO4 z*X=aFdL>zDVQ8k*hFw7$9?1r;T_ZF_Go|OF?MAia?YHVmS@-UGu1(%+Ov78&;M;`}-5-nX-< zhZ5xDZ|!`4#dhnCZ;RL7e)rx!cZVU@;+j^SuD`kL#>?Za##a8j^ka)m#C#72NpFv=HP^PZU|Pq=e?rE~fs<2V zvatH&ZOyZ8?~$`)<2ZkJd5zuUx3O;+`)mHJdb013fT&-{(k(tK?b$qL*zP`YX=fh$ zoe4tw?QgBQ_G(t6dh+8_Q?1{f_uZj$y*kxf@sxXHkjx~9C=Vr(!Uxa4eq6fRVyCmj z6~3aokL==1?o8pE6Z{*VcDu zH~l|2@=Eb-`Cc9IS0Tr&Pu=%Q zyZv(AZM(y+kvXfg9)_IJZ*@<*qa_d^q{hhpmEALWZtsh^%4cfY47WdbKPsLkWN`oH zE91!g@Y#Qlor+vm+2u8>-PL)u)7}*-q5>?b#YTn-{FejV4_uVFr}RROeg65mg5?Da zOhOzcxiz(`Uh}G^R=3`I)F2!p!@FdOLdzuKMy2{a8Py>@KHnL#j_Wko?OAbJWui#` zr773G8keqi-8b>R@71OamjpST8k!h8`hK2&z3k#iE9bS+9`-)J{)McF6*S;6s!IR1 zXt%uo_kUk@T!}Qhx^RiTU(@IJdp>0HF5l5Pzwl(^Bz51*eIIV@sNg$#(_g`yZFy*U z(UZom)O_D&f9JG?RX1Jz6YuHfz7Kb84?ccvAMmkZlDeQx=ZQCN93MOF$ zlUY;I;oH@f@7Ac5Y2H$lId;^4-m#|5LKYIo{vDed{b=!utG^$etoPkDvv=EW`-k>| z2UVZ!n`{*Ne1h_kM%xXg?>LWIBu86qnpRNvSwp2d>{G!{-8`2pqqVPtMf<};FPZFK zYHO-BKVyQ!13AkH>pmLF?`xMaJG*5|?rPb>=#QK;r!26(bg%6{gLD4v>$0moW*_Cv znja7=YikvwRj$&>eCH;U;}3&sw{@{y=bASb9IPq)`7boA%W0yB?~Tu^**BDjE<2cW z+dAq$gMZTPvZ;ZZr%Mmm|47qv3UTy4GQ)XNb*07eDZw8i)plfFOB2%gJyYVik5IkT zt`DAq${~6CB3HileYDhiXDDx|-Cb>$6-(O>dfit4Ty$W9|BU13T6))2t7Xm)e8PCp zWIlX&A zk560IY+E&7c3MTuqCDQ>lDB_PZa3#HnVzPoBRd|aZn)9NmFuQd$??(f^Sb@T zRXp~o>MpteEDnDESG3A)rgcop^XvIpTmQ=JU0!_GYdv4++p1$zq*7KsU8$cSlvF&k zFV6qvD)%GbCc9=Yy5}I0-%~fW?7>Stu4$IHXG(l~8UCs5(d{iSZcho{b!yhC1tpNpbKmPby&>h&&lhZkp>kH&XYEeO-9J~0e@XeQ zYnB)Hyn3-W?6z}gT#K#-=bzVd;l}UYIlf(J(R9mX-OTrWA@Qtjo>R0_0$u<8^-W#6 za(U=o@z>4kMAt21j@v0Pqr+(ZmA=cXs#LFjJo(>%8vP`rhT+n{tO^Rj{6EYTCR7NB8Qg2+WH=UFj9Sv*;d!$Mv9X z4?aaJ9eaECxc=_XAJ@r$^!;GUKD@McckN5&klT}1oi7UY zdyu)QUhFDUV85r}`y5NInCeM(cc;fi9WFLIUZ4Bd-S};+_=9sQ+phC0my69lW%u^g z-|kA;>*0l4?!?{|)l7(#Sx{o^>$We&MlNHaS0R^Y%f~1#ju{~bc7O114dN(0^{Zy* zv-L6V9cvn0x1HH*`L{A`ZN9(xvzssP9euU-+WE-5+tRJKPFcIY4AnV%=90Ycw_CYd zUjt5sEa2|)s(dDr@N(W%)~S6{UoiZsc@eT@lJl))-ley5{!J0x^XgZ4$m}AsbJy>d zS?^MbarM2r>)1RS2iC@yikXv7x@gS!kn9o}ai=Vy)Aae@9mh}4Z`GQ3+9S+y=ZC); za@P|>3o1WG`kXfknzVI(A^WE@2V`D*XC#N}pZ=G5{xa|B=*z3$ z>Ym!WyK|{`%H^v8lV4xhk+AJ|*5?BUn{V*Q&pLj@Y-OdiyKr8u`Elom4}I4KK2m13 z{BUpY`~M95{;SV*)@Hx`a=Xm$>7A{==jTOd+}ppgda0UQcf4M%^6S$10(bT<4t}jjt*m+JGGWOMn^_B2#eS6x@N$~A$nzbCfqy;w&gT14Wgpo3S=QAU z?mxF6``lR%=|^p0pDxutJg`7m%R`92UjFv>V_Ub+lruX1uIs{?b&iR5SCrq4o`1Uf zDyPtr1v-6^pMTZwf4yj}(z|WTE%Kt%S~8t;+{+edO^UJ!5$gNkUw7m9x%B>A}I(;*XTFu1g+vj9gOR5Ck%Fur8eX1>p^WLhn z$J3OB9JAIXO+pJ?trfj>oG9XyV3uIi9l|D!FPpNW~$ic#y-yl>F#Fl=)I-Y{#>)%_p$_Qtjvg-;@P@B+>dDblN1grJW4>s}t}eAdE#bh*sX6)h&WZ0NPqyF63GVt#33N%Imjxf!>!Ci~rDeY7*X>e)UQv*rBySxg(IERkS-{M;?Z?+iiN4 z_FU3t^JMYw^8Gx4W#9Y7S_?xSul~;vIhX6kx7|lK{b$%Cb9?trlg7hYtG0)%eReN6 zsBKELWB$CLNzM~z8t}Yi7kYf{XcWgf=j9G=lRrKGx^DV`I?+evAIqj)EN8sG{@M=F zOKYdr|7w3-_kLTh?CmYzu06W5V&~m?(#-{G*F7U&Y}n4tnx~rEF5}^y>B*qez*BDG zt-W&zf4=srudE&S?=-)RcT?T5xp~`#k`=KHI>j5F@b6z5n71(U-ko(9<4$DFnt#E2 zW&WfnZ8wf*lhQl_Rc$|hyPPL}{DG@D1Ji>CEAD8@mVI1cah|7GQMKXt`afKrcP_g6 z#~1Diuuj;%GvfWS?o#dil_oF$GZ=-vjM@J9U(ytj>c-oP&(_+W-5ALbdd%ZK`?-~8 zbFJ*0oBP_oEz%Bq^>yvbZAuGYb(zaF$dum@tEN$ z3B6jp>0*4?s_PlwT9++a`>N~7q>zgRt8Y!3_*Q|_t3CNw-7n1@IjXlbxA%NHuBGPs zX3qD`TYZCrFW#4meRy3<;88TM(G;Q9sZEnO`)@o`;{3Q^jhuGnnX1k5I_5T4J}IAG z?rsy>v`MM4cd2{v+OkEvUIw&Y-WqhMU8{6yO^T+W?4IWv<+GmN@{~O+!`kt-Z>RL- z@`^T5|B4{1$2ofqg9|lQuiI&3d%}C&gX8*H;rCc7-~O6YYR#wiDT?U{tNcrb`RlTl z9#PmoL6$vB>UO#Qhwa*$?@jb9{XQ}tyTj_0(s)$UVSdKsI|m!@I5x;8?(8v`>>;Q9 zM^tpyxo~5-{|u*J`sVI9cRy{}&6>q`%uZ=txw6RHLG3mnc>P! z6C94XE6V=F(gluq2cZ|gfT-GAp!DxRO0;k9qs#nQ#w zHoQ&NN}FL3k(&{qx|K&WsrKT3hRJK<*qd(X*!0gh|1jXrp{GY0a%wCCf@2-FZZBTG zWyfigt&^m)Rv1hX{oWby#?XHG_bYeo+h$xbh`Sv2Z{8Mtp-uN=B(79mTlXRF;ij-o7Yey_TSghg8U1e>% zW~p@F*RVaNo_>#47AVb?oy1=8e)9co$?lFHW#4R9Rd8^)KVg|cfp~=XwB&v(fg9C7 z=gocBy;U@8{nqN*r;0Z+v&%o6mgyH+maydH+sWT9bGJ`=^J?e2$EPM2TsnFx{XfID z`%zo3M!#Nk*X!2GH4E=dnfANMU1Z(TrlSsp1{MVYD$Enq_xWV4E=CS(vs~ZK=_GhDiqVGZ<=AGS5Djp2BRLF#o&U z_Z@#UwEcKgrtg|5r_>Vv^07#N%*vT%JH^EXw#^K7|5WrQ$p00`$41#N_S}!fiwt#) zmY-Vqs4VE1hs(j2-oJ(Ky52HBdu9q*am&-r|OZF+&+|i=#_cW_PMv$*BakEXLFPWFd*2z3{Ocv(tpLza5(%SDaWw*_u-Q6`J7Htl_9+J}~J~i5P z|H|*yp?9V|WL@vvmD{MGD*teucaO-56~7yT;9nEc6YC>? zH~O_}=>FB;r{~rN-Tj@NcYVLhZLzy&3tcPIZoTGI-8SW{Kt%X@#`%Fy4yUk+@|-H& zb!ANo_bv0g2N|AU7P;K{;ff1?esBCsw#{FpUvaPc&)^-t_Vm@biC@LuUV3}>ht%w( z`IT7A8(u9 z)nDBIPo?PUq}4gKsX5-)-tFHu%Plwj?acf{Q`46Zgg&jlQTt?R{wL0;u=4`@T(!I^ znv&)}xZYvd@znfTL0$6CtwF~RzbuhB^oqZ&{L8wBf9>jp>lfRvynS|i#+UD_M9pen zN4-qyI=S<0-mTTzb7U&F94MG}Ct`xd(&Ag{2D>gQb+Amfx9Oi7y5xbl*~6I|9@g+{ z?LKF7D`=j^BS$WuGt7M#Z~XAU#YRa~L1r98{JGfMZomVE8gN0t0%7!_tb;_rX|A?4eZZAaSrWUYL8O)4F`8tZ3; zOc$AUQKDe-jmo~CJ&&K6L?;>l{p4vUzc0(IAtB|8Z9&9x`?Oa(cQ@02DZCJ8biSvoQ;X%X4 zZ_>TRd}paWcNI@p4VU=zJm`W{v~TN5&6A!i%RK)x=;u60-d^NCmz%?2-?FDcu`6;| zp9f9JdbaG!!zRHalO`ys-<~nS|6kc<^^EA`b8Rj~$EMf^&bI$&`occs&Dt%GufC0r z*>P#1N~_YM8SE1rRUBJ%jSac%jaS`2a!2;r=Ct1Lj`QlLN2Pz*dpr8htJibF=YOnR zwoKb;{<=W*`2xQZh1A&hZ=ZiZ-q`m)LrB^c`T4)IUue2)e0(OYHE(*cC#Rm~@{T7> zH~%wCR@M@}mGbZ8^-E_`I|LRpeEG1>yD}*xdwbsd-<8)dM8@hW%lGi9+tfa2{kQzg z$xrUhH;(dmMxNTv6}V02w$0x?I|IsU9MYXrsvouLomqIQYWr<(UjL_@jzM!HEFXWI zc|PaW>Fl&m#vX=C9xt~l**;-$E4$^VW46DpXVkaZ2LzYBtUaA#wf$mj<|<8@J1h&g zHmy`Be{U$PFq7fse+Hg6i`XPouN+@yS($!n#eE5vg}Z$o?TXnVDl|XPx4~iw>0^L%>E`ZOgT_J%4o>^N7_FLh;A$_h~hk)>Xq!V?N=_tY<`ZnJUtd11P000VQK zeV^v+J+&$ea}OqM%kwtP-Spn;Xz0!-EguRQ<7PH0&99W-+c?Cr?98{`K@SEWw^w8wQ zO!-#DhnqJG&A6F){mS2&d(FMxYzXy^sDG8S7i(io@;gE_pWmfM5l_Dh|CF*O9>G*duCK2jf%vDd1DSNxaVI(sqMy!Z8{ z^;@@Hy}fSFouZjXFWYd1H9ZdS_O=L-F9@1cP}Jsqd$;67Rfj${ueU2wO7E@zX0BOq zccazi<3FE!&V87;M^kR9?DhPR3yO?RRP89QAO@BroCZO~ce0lh z^r@$pvZr%T4Za}z$tJV(I^*0a6Yk!({kJ85r9TDnPUV?&wkmS|@wfDfiBd{es1V{mXOQe1aZknX(uoDHo-9b8>Tb>{uub%n zt>J$L6VIG`;b$-Vzq*sbDy!|`#60D|LQ%sTD;QZFdgOnaK;V_EVPmD=F__mpZ!q@CGxLc|P;MG1#S> z#LSp#z}H_TQC)sJ#?_fWI;KD|t|L14)TH%XTuSNtcbqumR+=!o#Z|e?VB5lf@6S!} zRd#zEUr}*o_Vl$gixzITj?G)NFJL?G+U){~%BR9uJkI~h=FFMqux9mgn;AuNGtT;} zewp;2A&+^hYn*HBxeH&-ZU*1>JLk#MCKnZ(yuqtB$T7$z;mF^QYkcdaTtw@4R85ol zYxwYawC%yx(#GkcpEu~wzMZvqN9L<_@7{HFPc>RO(KG5}i%Xx!438Mep9lYzOz%|* zPn8N4T=iLD&Yz7gF?T&)NU{~539x&)H1!p8fvc^m(VXJ;W%5%knQbOFRVVb_uZ_$} znLEWZ=jha$m->PeIp3R2j;hWszH&>i>~-qxf}Q`E!y0a~YCQMaFo$X3yGM1;*-VjehS8Q5(DKntmA^K#`>M6-K)o1wU znm#laI>{*{>smSS#IGNhqmOeuy)!NNG<$tj*V5%0tIfq;xB5mnl&W4?>bO5+%8H*& zJ`P7J->+DhdbLwLXuFLJ|G&Li9?vH;?*Df`aN_)`**BIKl`8!$x&0;S+3u;bx~_-T znwWjhy8B0&_gCLu-Uq50UU!0~GIDyVG?e7N-E(dA3`T}ewS@xsy0#mq-B_b18nWZnN&O}Mi4rGO+;|SYG3n!PU9@A4kaRwC|5}rskG5TS zdujgc^is>6zbXTFl{0VWs&0O-x$)&93z2F8A;*))zn^%!R3%mS*!`l;&@8D++i>UDC_#!x8=hIToqsked zdtb-?UV7-kr!3L@72doJ%yG+?#R$}-8I-=xlbWbs(L8^~8q+hI8gH)G+BjF}*WCNH z9A$64vleZ;XI7i^v%WQC*8($tI{p>pq?REX2Df?q76U#w~D;uL(q!gcIp5e@@ zoGY4Y_B{UES#_7Jy7}+-+TAecQa3j;w~n5Fp(6H1smaobZn92I+$<~Baiw{(xd@00 zKRmPXk@Aj}Z?CBAHe+wRQ?sHt`KH*sTW-{NN#MNKm#&|XD z+b3tS2|tb-_WY}#xb#kX&xYxnWu_mVnj8D_or%uQ%5Cw&QE#{HkP2y2z3#*$GTq{S|p-6H8Y|f1PU1b>Y!} z2HBwbg}HpHYOyg7j`&wxjB)#W?m_nVqb0YkJC&Qg=9Q#e<_No6cHc8wJ4#hcQ$f>x ziqOJG><2%u+Ot;ZPTP!^-_L5cz0kR(@;Ykw{EWTvTfWQP-kN(`R_V@{RUv;JgV$|f zsy(~mp&Fyzi_V!Rm`u}uRQDZ8Wnr6qhH-xF>lrnrSt&(_XMI}rc>A~3>`;@iuBa_{ zw6460iU+aGVJ5?_n~s4hfkF+N!szj`ghmH+j;Fp zk=teG&Ca+g3|u`Yq*hx@2kgC`K~($cz9^g-?CQLtfu@opgWS(&9Pwme(B~ z3NKW3lQFPPmOS2Dzj(g)JLzfLm(^OVznA}O_tw+A+|xyQ1Vb_}#IP_qD!K-_Prk42 zRIu0S`WEYn+w7lLJuc^4UvS9n80Vpj*6XTcSd1nGg$gqo@K3(I<6ZrI*TY5fnaQ`> zS(H;<-L8I`^gtszE3Bf4d*USb6DR*3+g@F?X{N}vXR*;za+O-8F|{shlUSCd8b1H_ zw~oIi!+?8VyC_3zV6W3vRnOqA46*b(OU^mWpMT@~@5~tu4_IzB?b+%Q>%7*{MuB1L zN>zuI2JeR=!mk4GIPeb_`Le)lrH4Em|LiAP95h|ML<{tvIv zBhTAGLWa?63QcBaT`AZ$EhfT?>2l{AHG56wlal@0wr%scTP!x=@vTKu1CIq~3Evg} z9d~W>_51nxY`R>co|{>WwO#45+QGT4hr&W8D7x_Uy%aK*GO}*pJTVC2c6s}Y*;{W%PHLN|!W6<8_;ixzwkucdvhE7{4dYVzbqC#ZNh}pbDX?S zGpsn}U-+p0&(A9#RRrcNI95}6CF|~d-B4@){|swNO+vXG7p%z4*&!=;Jz8POGCqq5 z9+M4Bx9nY#uxjocnP-b+0yvEhd=`BsUG?Vmw5xY_PtV<1_(yq)IoB#D3mdMz=F=J$ zzYRIzVXQ8=ezL{X4cYBRdOvNW|1*4Dzv#i^lQ-2DEcBk+J3D*5($cA6Ruh~i%zyfc zMfmo?j(*vHrg!pwtl>H&;XV0b@88dRi&&>vOFWeB+p{8b`>J_mPwqNDohlG^;!qmH zp9bOY3XDB}<_f&H<$L5u@AT8f4=0q#Yb77bTYUa|c$n1$gKV!i%U{ms41YK|V9UIq z8BrTl)#@1D_WfsAwz%v4HrW|dp8kA1efsjP5xR>alizwb_dJ%keQT}xhi|Wrnw@>U z)OOLT4MEZRy8nbPF*<5i-&s;5ZMD&qqKHgXjwik{_}B_yYyDii?`of zJl(P0-OsF@S9#wer?4q&8XAOWSXwd*B-HRXhOaAC9C z&P(SffBmbmvFMpjt)XY_RDZ9k?knaYCqtd~bF}R6cozL=k-`KXj<>f}PN=(`)Go`E znpg9)E<{CW)hovG<)*&1y4>=e>sBSroUs09H-kzFrV0l8cS^r1_s4?v+|o z@+z06p4+4Q+o-5|`I%s&8H$S}*H!B31YZtaK--iJ^Mdx&vwNh+%NLC zElr#u)0Xqk>gK)Szp{7Ux?UQ3?P8>R&JxD+zCLU9t5ya0tY}~`RB`F(w0brzXm5ju ze&_|BPs}QBuXMe>_>n`eAW3=e!+o}e-u^$NR*IN&>ArN$xD#EqZf@D{($H)^ab2$| z-dq02$jE;C$Q=;SIpw)u(&GyU1t;B8^K{p3y!z$9pO+uDbPDL4Ic3v!{NB>4(hIux zcYjx1zw~YR-h$hrUp@Xa@FcCjbJ}l3!&EZ|8;&WT7$liE8WqBSOzyhJ+G%E&!uZL4 zYIbdA!nJ4LnYk*PDyxcD-@RP_(0cc@w{Ls@7AI##Pq{Ta`Hy|`x;+oo?=2OUVPSP@ zP?7j~_0#!>ovPaQBD3S>c`bNSZ}T~3@gw7?^LsW_$qzvmfEdMMVU*jEK4T-b&Qyw!ECku_MbqVAdOI!ACI&4&raFV9GN56 zyx{wizq*^=rsZ6+jrq^8V7UW#q3TxtG6yxL<;UNzzR#2So=ug3-*~I`qzjA6zsVNv ze|&1`(Mh|e_1=1I6k5KBF=bo$(HuP$=I1I0j~r{6-BCNs*W8)=-vvWg$ApEY3Fd1V zZ@zdx{nV``ccs>C4OpO=XSD2Rkst#{r|p9I!mE9<8H0`H_Up_MetT`=$t0ECN0vMB z?b$D7mUY_O>(Gj~>n_hS;8)qvv(R0I$za~y&%eH372Gl9;~nm_D>a?5pZ$2761ElD z?9Xcd&mg>7bM2PcXy0h9DI3~i9z}b#7pd==bHu$jQtV!A?GlF7>ZU5f``gUyDi`KH z54z=W{CPl+X-uT)nY*vo&g)*txQ<~8BU3^%%YTM7S$gWFI#1K)I_vwq{k&#*N}q=* zYxUAqJM8qD)~?WMSae5Mi$#q=b}GyHoLA=${Fr@Aidkw+tj){PyHkohr%2S#SirUG zck7BpeY1PBn3sA*&7Nv`{GcTRvs_Z~0_CQgSGFEx`OmQY`}075xe%}B{Z~rXh;tv- z)!e@B+|p%TK~t(7y>sRMZtS~YXZNe=()+;FZBp(_O}aixmp=Vbx5azs+v?EYs#{MB zR?hmilWV!)w0kze2c3$~Oj>EozJbYPp7+VHgeAF;U3MK2FFjY&eyt@#e&?3IwxObu ziz>{2R88HsRMDT6$zsw}rUed0la&mmbbrirIC07BU)ttOJx4A!cYP_Y-8r%+_q<*= zHEizwioLr|^M!^;xruD_yPOoZlx32W&}^!)WrhdFYm@Nsyr?OCBQr)iO=|B-n=MQ`pCp7iYD(sRcTOD+$K@e^rw@-Qu3 zb^6NhtDV0OWvvb6V_EBX{K5*mM@}sh73ST1FK7Mo`r$ifk7PZ5Kb~l7o9QpMSMX)R zfxbo4woZ?lefZ$+Th@I-@Aeg>FUnqwsRLHzjmL-yF21_ zd}rPqt5-ro%VU^4&P;jaA^-XpuhOF!&tNV$?L{BXv9&3rN#qM}75UF_xSU-yU|E*r zquX{Xzp{uXGcQR5Jtt;fDK7Xrvr#=O*7kto zEyh!eEL9wmf83`1tX6#)59iTU$4E=AGlb?9y{vH#10j`P$k?X(AgK zEIxl=ueea;tr_bs@!{N;8y`OBS)ZNyuk^Xw6zj)DZ{0F)F5Z1*n&!1oUrt6}jhRnc z4`>=ss4HF|bkA~zP(yW4+UDLF0&@?4{jzxO%j>qcuU1bjyt7^{)FsQIBIbdM(=+zm{YQK7|+`~UKSQ1_y@3+^weydOaL)w+!j`=1bFQ=Xo z@bG*ll2)KpU3RtV)77POY@F>Jia#B-{_Vo(pM5v=>2v?rwth#KZM$UdUnY90__DrD z+rpA}k_?=kKCAcDWUW7DQF~D8sW1Py>oZ~Y?b6{@gW zWr@GhB#X5U&t>ZPcRY3~;8dNJ)AvBOlKGw1)+1+x*Jspyx+;FhO!nZmyt30b7O!5q zWQW1&mHrD_)fTFph|W4Mpt4%HbH??=hvA!IoW4EvHu_}o^w+bMX~EMB-_BR`lbd(x zTAkIw>Sf!n-hOvwD#!Z8doHWR{_&Fe8X$09LG+zhLf?Oe={p&&R$P6bYRp^Zamx zdeW7Bqo&HiCx(J+bz_+%RT3hDVN{s(BydHN~}lF!!06P9Bnq+J>p|O-t%q#bytsB{i$+CLd?yc zGT46(*y5-dg=UB;)Gt?`2nR=36b!2o??3TIQU< z9@Nry+wpVjni)Q4$|g3wU6**wXYRubxn10yQLofkZ7-h*VqR?PysM;b-Q&x1zn?8T z#np7wG%E1KhE}bh3)tgh$$d6l|cM)b<-N89F1a%yTcaL+uSDNuO( z`!%6At1GIWS220lOl+KFSHHBd{?)9#)!ys(dTurMFYvWmEf{iX<*NxTx_Tz9=Y>8g zoa#8xEUZ@g`To(lub=DJDA~U}cx!h(OTxEPXM28M^}Bav?X(rsoaAhF>~NgQ>i*^X z@nqQtGTH0m3R}B_4zV?!-Q4^`D65n8>}K~{8xLPHv*!&7xaqL^k5f!^|yuItxNAr^sA^|vO(bR`^v{t-QRInfA>nixh(Oh9?Mh> zMj>mLqBr~%=WFeLlyPti<{c1uCVk}dQSHZmA&DZ@0lq7LKRa-zto!Y@ttHc~v|8+J zyEGZOzbo%yz9V;!=hb(9`5mqr>sog)?6j+`(OP~u)@7HRk>{u5*Y;+9v962WHTyE} z)}1e2WxV3P(zEi&lu4EiT$gMnSf4+(>D6Ro8~wirFT8b#kCs@HuzJJiWk!=0tW@Su zSWz^|UrzZQOWD6$vQJZcEEzjLtyp}x{&CcW6{*vuybeV&tW;=amb7jEK7Vq5&(Wf% z?HM;DUe3R@wplSCe2%hL?Y5DQ*UfH(PzT60@ zs)_s0O>ukReP?NYkh$o&=X)}~9nP$LwQJXuOBW6NKb`8B^zdyV%dUCMkzHO58R^WDEIo8*Vxd@s^}=j0QiN8DN-3=>+`vP?=+tDehccXrK$ zTQ!^#_MI!9#B#1Ys8S+*;Xi}Vug!aPs?U5`y~@Pgt6a|UKLg*&RjkSGkrRHpBj%A5m`XwwnQ_5B5sc)EJDEW7@ z>)Xdd%{`eX`Tgmv6{)JZ z$Nn?;PtAzmrgrO2S=1}(r@N-TbbF+lSvW5<$ijU`eIS#E;G>#X(Vkm0xr*n@pUL+* zkj5D4sC`8yT)ArbADh{C_B!skl>2vUMDPBH{|sjHm#S{`I%lb3wergWoBhibmhc_O z?u(DS|DWNi^O|J$n0m8ybF)9HcxLSS_v7jQ$B(_^R{v+>fm_x_P@1GiLVvkl%4}(`n`A+(N!&na`K6^q=bQ z?uw2rcdvAxry-wxQ@nn2{Slt+J7YKOd6~Lud+sWclupJ?k30qM2H!Z*ytF@4Ca*L_ z;ir7njKUqkzq8Cgo35_BwR+37Eq9;w|Ey{H+17Pw-X+J+%8z#(Z(gKlr_h(NTf>Oq zmAQpoU1>|8eyvqqeXmxweCq4Dhq~r!EnS_%I%Vmn$D$m^Mfn^A*oz-OGRb{rC@m|5?`Dx|p>X5A=nW2Z~=3FUIJW?DqtKgup!czsIlw`?Ew`X_9 zCaK*Fo^bX2%b?n}WVt{N_UDdM<~J|fwQSb!B=gyfzF=MFBbZ(v|I4>1?PlTOSu<9#zOA*!zC;> zR`9v`2lvQNReZF^%{J@Qj-z~+u3U-A(0(n{RqV7>q@_b;9ec^QMnUB>FRC|ZU06~( zP3C!Rbe*W?-ib^2m)reVGdu0aUEbo-WixO6vOjFc@YPvMRNIrYV1|(p|I>f#H!?f? zXUMGIa(cyOkGwf%PK|+AZ*MN-QqP_CdTp%L6p^NFbAp_12Ygozs6DAHP*A-1;S?2z zO)t4Gd|g(y+4jJd)q8c!Wmj!4v3Hn#wrD?t%(DgJKLZRI?d7T;mApIMzjU{;yTr*) zD^h-_+?g8P(_r~^QB`-Y^|p(dn{Q{W%x?DJj1sd@ah$Z$?&TAXzUNjxcd|TXP8;XW zbL!mpBTc(RTS9>^IzV@M+LirLq48d^xr=J|-3V%US9nA6Kf}jm>yJ$h5qTZ@n~z=O zlV&QjzEANCEkxpz3=YN$={&!3Ltf)m&3gRb^#W z`*yZVE?MU7tGMxq#ZsM>F_oHLK~q9JRbHN5VepjcY1Mn@wZAehgcpBV@H%(u>t?M= zovO~DGdin`jBnLn-)F*m^hNlEJ%6-Iwz{)aT;QSUDKJH-7uP z#$LE}Iv3w6#gs<37Ux-~-tKCh<^G>R>b1$K3?{J&oLqLvJKrhQnTo`CxhV!ZvY*B(VDNOdWXF>=PKav%92u4ba4~mZ&IoFo$-6=+($=}nNM8WQgK-1 zKD%szJ@1S4ABtR~q*A9#D<)WQaQX0OO=6gkkbdUG$0av2uA~;()VExg*iobAce%h{ zaqhm7-*Ig~9hyQ# zQx5bU4=sDdkd-Rwc=^l9+&r~eNjDDt%JryQ^h@WK#>(5xWs3^7v{wqgEK+m!IMkFN z;4mauNV3suD%q#y!L)ztibK}xxZ%rHVJpr zUZ;3vXTWK(XVYY(-!-@x&C`%c@0#LN{owMCC2zhrTNcje>D`mLUU+wOwUcP&lj5V* zd%PwsYL!_tKg(&Un~TW5iR=$*w^sJ}E-*~xvSN&}^=eg^Gk>o0j!jc6A~dyw92nH< zzpeN-e}BDHgHN}?6od2aR#%r^GBaHsVEKv5!Qs&44=x_Yw|{4B*(vmH9hw)eN5&h5X=Mk@;Yn+o_@@?;d=~o$GaLx>UQ+EUQ!MZ(RXyC?efPvW&bneO6_r7 ze9%Y0W=~Ca#lw^{d9Fp1U#I76`OgricXer)m}p?$;!>}bTbC{Mo9}1IDl$*`woJ-F z&&evUC)I@JZHn8yN44`62lLaem-l%Kr5n?Bg*?8sdh`1)i{4+heO1m{_jJow%_YH> z+b{Zhv}f2ReE)oVP59QLZB7%Ws!!2bL&_uj1xyGtJ%9#4Cc{B-B{RXexO&)H_% z8shaT#rSB>uG4;In_8OD`26VLzC^0*kd zZq~ewIaQ_J>`ej_k7_guRUe-?=O~3wHo!Pxxx}0VA82+5Q zdgcB@r`MkHdUsc8uhl0liIOYP6MlZz6_~Qp?%k7We|_6O2Uf-0KKX6Wammm+mzDQl zM(ld{;k1-j`1R?J&+YaP*R`8&>#}sK6XOGR{g=YeRVFJxj@Y4OexaUgZCUnuha11R z`lCX&-Q_cX#pEtp_+8QV@$`(7?-#ybQg`Ode}>1=n>N(4JmHePBj%m=czb9-Oy;Dg z#tW8wvgva4@d}kw=?lJbe#VszvF>xTZtZDfKW}QDaKQjUn?YMW-3eXsuyo-59Z%7 zi_SfD<3&~Pm8M&q-<#@}MjALCZ=8SG;cTX+?6e-1CuS_aq&Bqm$%s6C8u)wj^56AU zt4;?;v~G`g4!fEw>zpQ=dqiO4J4wzXf*);<^oq(pF^XndEcbIwpR7b-VUN+L&x^%w za;I*oS`p;M)ih0nN5G@WroZ37WZI((N4)3cUnySnaAwSg^HB$~u1pW~jC-7@EjKC5 zN7c=!$v-vu>(5KOtDRM5O!i-D!nzsnvvko0=n zDaNX~2`er&)!z8G?(*-l-s1SKH);<*iOrL{#QbkU#P2UZ!y|uG_Z%;laJ)02NPVr! z+imUTau1f=lQp^@?rqdGTkqoKoh{#4xgF*eRnOnH^FIUs!*an`?tPNVI=TN;of1wncYit*>o+#T`=8;rTCq5re8;2g|vT>Lagb9Q>FReP`1o z!z*I9Pg^{H8hh^ggXrveG7Eauk00A@ySdz!?bWSoz2{^7-mctr%WSo`zeliU5aXjg zjK>w8q{qr{>U+j_C$acZy<$w;yDKWom`XEOvfkGH>Jb`hu`G&TB06$Pxs2$Cqy%XU0y1)+(x|Msq&@!uO!%?27kDHdv>Mh*J;duJkJ)r zUn75*z2+vj_RFtY=FwcNtUL=vUVHV~9?WiSMNbKdzS@hfUqhWxdyci&$8r8C0uv5VMsyMvCtljV}1tNgfZ zz55AYZ*k6pzUuT3XD$05Nt^e_ZfU8U<&!7ZlRtOei}iP;?Tg=B<6;W_@yb0q_ehV% zq022Nl&4RY-E!`I*8&GAW1;OzZeMhF-S)W|7W__YTb^m)tcxx!HW`!FpJ{0=I%w1Q zCMiYm^RgIAuc(<1)qLuw+f+41-&y1H%OuCD_hs;oh^z@AyY%`lYifP_mHj}3gGnL5 z$44e}>fVjhu17j9`Sa4VD>g;xdsV;q^B3o8F`Qw{t9a>FJ?m5X$DQw`zrHNpz3y?)-_+1zkr%oC zcKf`ex`L)G5Y=`0=;hEzV>3ZyrtEaRyq6;=m zc&@tEVWnH~Be(SZ`ExCd_C&mOPoFP;UVg6r#_e+#+0HnpQFl9aP3=RM-)q>UgLY`9 z2q-`37iQyI@}FUaTUFMtIujr76)$W1=jP|tP0w4qH`3MN?e6~!QNN@rw=B%CTNPrd z(!|HfB5}m8&Xk}3N1J4)_69k(#(5W)+%lZSxnhgjuk?Sk_uf(~2kqS82KN`$ z6;_))F1|c1dzPo7IGgd@;|XiG>#SQg{i8mY+5}-my)~l3eGgLPWBEbx)!A{?;r@9{6ShBV6m+x8+ zhp0=I<~@(3^#(eRA^7b}(>1 zUnzLsUh2}Tt518ktUSy&Lxapq?=6VY?EuUxpt9Uj4Vc8}7Z>OJ22x#X)&vF%ug+` z40Mgy6|_XXLq&+|f|rA)g2a&-AD4alsFClq&y9a-)Z4XU?1s;SW|`cYow_w-YjoIO z?`0tyK4@~WGaJsSDWAORxa6ZEy`+|8nX?|E0Tb9ZKkf^;ymD(-IP0ZJGiNPt5{VXN z*s;WedB*%#67N@7gf?-6Ro-U!a`4Zpjg$SO!VTs?nT zer@r|4rY&Eb)}pkFCINCiq~X$P&nCArDOj7%;UMqTewPIviwT;bG9-!a=NXS?xCgP zT)}Vq%al7UexGxZwdlNYyzfl&e}=UQH>SQ0$X}y*wBOg!k=5nElgZ5ce>4C4b&oq$ zr}~IIPx4FW6R*EZywj*#YQPx&@XCE7OIBu1^R3$(RJSe23*RDps$*x&{H*~Y*Q@wLbz3wKoURD^AS_@#`I&v7 z?3QchF1dLn{Yr(O|7KKow{6|dRe3f4?(bjSE6ozOzBS`L>s`N!=aVVVulk2aG_`^* zZSaV?B;*t_aq@gA%Um%B??i?V@@FoYJ>oXJw0r*ZUL&)mb&tJ5&iX#Pyi+%4t7jyx;2TELJuJlYW^V`K*KGK?dea%3i;=`;+#$a_i-&>)U7N zUR}Cm*+jivzijj;T@hf~<8u6bZZ!MhTe)ouWaRdo`0`=t^-Wey=Zt-P`y=;#H?@Ab z`)cUUoE0hRR|9!=h$uANbP#a)Wc;n~XJ~QOL@zNJp5i-`YF7)cm{=w%e_1P9?&d4i zL%r4ftINE%X6ISVUV3cj>E3+{SDxhiwVJ2Nv1J9%#Ks%P%N9K{c-5T$e63@i0{=}9 ziJz0h?oRvbdbwq)C5!en&cJt-k_KtTH;x~h;x28stg!iV?ca_5c{BimvIeg^wO+g(UMFoDPlc*DHBOvA<5*Ml?2{rw2CL6AtKVOKY4xWwALVZR zXDH-X{jefx%gS3-ORs2O$W3%v>FSzf_|C{u?uVq{-~JzElKJ@!)2AC$rr%mRDN?C* z?$n~1vR~J(@`majnm4cW%i5GJOHNs<921#r>Df5H|8i`awd3t?*LkAlA79QF^qBT? zo$kjCU;bv+mv31&@xtq@+EdFpy<$8ynfssm%Y4`T{OxzPaUpv-U&Dsn2ATGkP9NS$ z3uy{}{m)SLpF!)|%k9D0yZo>CZr``UA^C%;%sf6X@3k5tYc-i|7H{bJsKnB%^SC&s zY)R}&!#>B;OtuEDCY!hSFo`^=>c6x%?aSfX=&IQp*X-^)ACi@+b<=r;LMxLPoA84t z6YqZ)URZbG)XWB5iO$_Xdo7CpmY2YaLNIBr{h9ZtI=YRhhPHoOo zZF$nuwZUxG^LJvuvaYXKXQFR0`ITY79bK;TTMinrpRjGXB+%6tWb(dLi9OdI z32|_G-O*Na?E9f?zot@3?^G(gGw&)>ht7!+t6xa&3cOa-C( zB6;Sg!ijTiD`bMW_lfNK9c-dJi=XSIjM~3@AKS}a=5oBM*II8n*KEPvaOt+cFLQ5g zx>*=?Y1ZkeD-mC0xda?onCuPbtIYe%JR!bDY2=cg0>kspmv6tfcFJ__SXbTDjbBtVqP@7h8ktZMoFS`cLQTW~?sf|8edU$AL)e#WVgh z%!+z@f6?8?t~rZtUC-Mb`|4NOVU^iRkp+TXGkwAYA3^3@3NEjYKb-VHVain5 zP!GAEYBSW22ArI3nA#uP|1|vBMQ88itru>tJnQsgB)t^2d4P3vT`&7geey^FCj{E&5_baO3)SRehXJ7Yg>LinQE8mseTI|d6dHs_e zAr~Ip_R?K)sNz_)$LEQb=QBR9nD;^ay~2U#8y8EZ%HBHgRw>SlDd3ly=Od0eb&0q4 z$iH7O>y~+0iUsfKd9^EUl#Pl$FP`XO>iI@QM{8OE@_VYL`4e!hb!;)%N2QUf1gfv@Yw^*UcaO$=>h3OK z3cBQ@xG639^NMfppFEC{{x$z&zR0{k#&b30bvMXDk<~?~a z_0Y0K*ORL&a=+hjUa@1{T(6wfm$`xyWDeB`vFA&jXMYr~+OV@W{kW(6+^Fl_u12q< zlJdD&UyA={IB>mh{yi=8SpoV}R(}cfn)v5?rTd>P6I3NW&i{6;PI2r0%1IIa#kubF z*ILcDdv})VO+Hf*YV=CA_3z&L9U&37*XU|Ab){eA+tMN9bUgFGkL%t)syjGN%{cet z#;31q)gOI4QNX}uw?%gQ_R>u^ixyND8y2rf)o0!BBl=>;FP0NumE3F&{x!MWQLs}l z#6CkpuEO&8iZwS2RTNu%m;AVP=zVq3qGj{eu69)Q*L3g`nECC=zkH#(U;e7Cr+9g8 z#Cz4|T5UJGQnSHc=HlZOTTOOJb=#^QdhGUEvoNzzantmqkSP<2It4hJ(yCv|Oqz6V z*T=9QmklR51U~ebVC=;Bulon8swJY|2UzbkoVJ(qh)@2h?tAzusjk4Jq?bBm7t zh}s_PcSxj2#-_V-rqKMC_WAqodqsDatqXO_I-75vQX)4mey-Q`n;Q4@L(HC~HyJ+m(N#+ggro4J%Rgf< z@>OT)H?di>+~X39ZZ&E=^YT~GVB)yiS3RxOM9JQ?I5A>^KMUE#<4hrc^H%%mG1e&4=& z`Jw9vJw4fMG{XKf)U1ES_i|^9blT&P8}mIK^g?dA|G9epsr)S#zn%>ncWiCoVJe*5 zI47xp=OZ`EFV9&n3r)|uZ?-lt>%Et7+%2K&ix%tl(FEv^zsk^fMRnz7F3b1$kv?yJVyl;g#7jZ4#x@EEaA;Luz94}DkFa(}cUo-=sL(h9Gp{cE?**Ws7&GXzw+g&A}0nyLGGmX_hA2jW-g`&$Rteck9f$v|5GtpMG7L zc*5|msJ0XLy9KJcJN6a6Y+`cgQ{ER;U&Ht-``VjR_FKvp`rgU8!Ca-f>?`wHvsc~v zq6>;WlsJS1Z#=0>e(>$erW2Pm^tbWdt}7|5Y|6cR;(1K>KZWR3*Rx)K*dBT}@7wpN zjRE{#bH86|X%bt*GDG(3=j+Gk3q-05mCk;jd*yoC5B|pXho{c$>ht@0_M*@8f6G>$ z3cnH)B^|)=jZyM@&zzrfoIkI}%)487`+f62EpfTu&tJye@;v3H`zvf|1n;Hg&0pRz zg{;%GpQ<2tp?1lHI+n=|%E1xQ?&7t4bACwOdUa7sM&C4hAX?FkE0PRk3&e0rq!ul4s`?T=Tl?wi%t=o{IYqoUF(P{dPyUeR7b z*7?Y~rVUN%Ji_l8H_A!aZHZ?QvT0S%`|u>wdFQ0ZrCf#6VnWSD z`Mcby&`u>yd5e7cyqjmEb9dMX`pA5?waMOKe^~le{2#NIu^0EAE?6C`n0ni4nuFFG z@7#%kPa2=M*Zo>+dqj3)=ZuyY_e|7}$e7A}o2dW!`r7i)Z?l3#UVC}3)#KxuDtAL= z;?LxhzmuOwZrRql%u-!W@@Tm34H@R|t!^z#UAf~$KKd(6S*fYp@$peH3$L*9vHm6N zCO)1sKkM#t*#-LhTeEb%Gqybl4DJ+~6s%-;9_3*q@e5%R( z>bKY1%KjdDow`-;eQ_?A?xvpBBgwZX_sqMy*h=Z%jTpJxVbX`%r$zjBRI>~y_y5&i z^J?FfJ%M2t?v%~FyDns%Ri{(XLr+!@t`qjlj7nZ!lUBcAE|PR?yTH>j)76*Gc3#n) zTCx5=gWTOydn066HeJ-yX`RFn7<7J1@qTT7OKN^|jx=t-M#zx@*}ars)#G^Dq4U z8aufq`pEM)s{;E2*k8N*A6d48Vcrz&{lDV3?3$ReW?PTgRaDKsi!TF;4%@w-O`FMHyoT5WC)(JUHH(=w>-~Pr#V!Qg@HT+sLpQYv`XPujNyL5UW-{M(bJ6Rn4>Q8?% zvd_(xnQ)?7Okut}*USiw)1@n}XKKn7)?8X7CK4h#n%BbK zc>()=wg1{~ucdV;l*{w?z4>#CUOh}+vOM^AGsn2e)kXfx zz0|uk)bi3!^=0)l7TkKfFjw_>3{Mr8f=KhvGfjrKjg9ZiZ(H7V&0?|9Ek55h z!VmFYXXOq(nGLe6k^i=Q4NuD3H^)3L@7do&J8lb27Y=LLwq##GCEM=LJ`=eP+UFK} zzf6BqZBe{Hu2Oz&si&Q!v20fMxnn(A9nHfX9yW8-LK=iD4((<< zYWRIp;bOrDm0SNFsN|a(v}KddaS!He`(8_wP0q{{({^3Ab9VcpxZtx(pC@uXX=vwA zl~i&{m~oCJ`+k1t^v%5n^;aypb{_xmM`*<)pI32nH9dS6u96cudsS(z*0ZLB1?qP^ zIGRFLmK!tp%~><)gvp7rl@{%s)6|4M2fCHzzPntbyEP}UD?HLUv+!8uj#HE8XV~s^ z7xFMNWZpN`o~7mKu3z&n%~qZ?>r?Tck4JOj-R_0GjFP-NMLf!NaaY!b(`zq>>UvJP z=hE>erJVV|nLk$S0u~dJ{k7J7`fS*zvbZT^or~tdk9K_S-;@Jl&uUJv`kpcS^wS7KiIWShpU&Df z^<3HYJGqK3(<=Nsx&HFFzp#0A{@@csvz5n|e~>x8HZWh0OK(ki6_?QV=#a(;HC zxO7FmexnEH@roWvBcBufR{2NrrxmZfU3h81!xw9MF7Y1MN_e|k*GlA3_my>bgpX+I zum0H`B2mbEfW^~@KUZVk$JqLvQ)D^pdo3ScFL)t!f~ThPcW~QDpW9QztxlVUminq{ zb7kK5yl{NgR)tR<1`CB4dnU2p_YjM&P~(gDw>^2%R>RNW#Kw7Nzu%s3SCYLcGX9TU zcHYJy*mklX4J@l{e(n9MChZr!tVz?J`rfz`9VBot^~YE5ITJ29 zOj5L*z&GQ*N0YOiQBB|cWq($d%yE)Cxkc{b^XG9N_sx4Ro3#5&{Ico%E4WkpE?!-( z>$|&h`Qp?JFD*VVvF5kV8(C{?=bU`3&}*=5hgRb|_S*gYhhN9Ice)CD9OB+cUp#A-Ni18@wOwe-p+kK*69B z*2}ia1Z#SF22E_4%D^)57RT{_0yQrhVz*9_SQ!0g@q5dt%c0J3vpbv*E)Y^SJgLT> zP(M<^+x;a!eq1f?I&RV)@ce~+)W2JMg_bT~yKG0E`Aa=tqm4hR ztirr9Q`MNRc}^FZvEN?%?b>eaHpO-#oUvFKdrxP5nQ>qh` zce_Aq!Rlz6uFqVFcN&(3IIzms1u8gabMM}1w0Yuut4al}na{RbZ!Y&$dv*KU?NgE~ zmgGJ=b>-4j)pn*GYq$CoJz5Yvzl_m}>0797M+0xr@p8$Q=hORbsssOWzm>CJRkG(Lc5B@Pz)} zs%ua0JbS>Odwy?P^N z%=|Im-tyQs@7Welj54my{dT9eY^B0x{w+l*6>tADm@utAy=B_h+xIf_0#=Ai&y4N} zR9Nb%DbtjeQu{otj{V5BM+%de_{!e#uVh+PFI@2Ap`(ufy^jq4O0~CNJA2T5cFJW{ zE)7l_=Jqef%KP&khm}=G#x8Z@komar>ykZIcYAgOG1#2@xx)5h!dfqZ#~sU^Q&@Ff zQykQ7e|O5+zhAQUh;pwO+k11{oyi9tSG~B_ByvnxF6E!)Q%5oFueTH9-rin!HR!f^ zl~>#R%uMAw3pT9$&LjEp+rcIitGKe&okeReWZeF+@_j{1K6}Os4*N$Ox6kbju2raY zx@q#bQf)lG_isrvRO!F(eAC?`;lUHRnEh6MNv;RIF09V)(DcYn5t??KPle;i z_nx1!i=*PFO#R|_EBZv*lD3MATMf=n)h&Pbb5qH`W2>*e6cy!_$lW$0XYKVVOBnfTiHeATwtZ@V8?pSbpRdfc<8Msp$+ zS(p#%PIVNre#Eu>xo^eN<2=9hI&KSneK)DDbm`n9J#Sa5Uz%$^JMTm8e}<2|yK8be z6unGLMIM%%YElwoh^juWXm95euqZQN73&r0mCrQqgzIpew`Y#JR{KM=&~bc3 zRd++B@$c@Jxn~5XUE{MYllPh>Ca}(5IX+iJGiY6Qj*`(z&m&9HcfS8_zis*+Ls>)1 zck|M1{`QJ(SMuLu7eQ zvcE0lvzzD7$OO5@I}8MNw2Xg0GCB59PWAnIyFDu^)zov+ zwl?Z+d#=>7Fz!(jSNp5NkH6esuXwB6@yzwke5si-tS3#EefTnMrT?-OoZ*b`p72ck z_HWJOFIIAqTlS|dPkyHD7T^}PSUyPPR#U}}84pi!N>1jqxBSnrtb5Cwg~gL6Ki~g0 zP$zr2?zx?(zXo2M`c7!=+0134p_=x;x{4-ocPa>}+8$Bk{2_1El`-?&GO5`Ia@?QB zZJp(!E+2nC>&dl$i&iaLTiDfAajpSedG_$L*4PD+uS+st72e4g`_ zi_<=r?>_8lo3*M`eNDA=!nLs5ukWlYymrgG+bi0;C`Dkh8h-`*`FEy!mwwT?+rfS% z&u*S+`NkiiPdiRrTETwg$=~ehv61(u%dLJ_o@cJL>Q;5oRk2sDJw^?QnvIP;la2gu znP&GobhHFHKJ)rF`|^)PzYUJ0I(`%>-uSEkinZgFs->D8QJhNNNq3GuUaIIVL%SaDe#*B473&p)Rm-x;v& zRbH|&Gr&_$q(tbpuW-Aho#a0* z=ym5=v3{|#sM4KBVLnM)Uh7}CdV1~hR);Ru#gk$jEnf$%2j#0OSkOLs{EK`~^{p8aTdo<+vf9SnyE1pBzRss}o)eGM{IuUTt>9+plj355+mmzZ zoaWs-w}$QX_H+AnPCJ`suHBQT^)1Kb^&jhXsw}2wEhRhNsGl!fb6zX9KFdv^g2A+A zRm3`_UdxNGnx$9rZOy3Lwo*~MaM^`coqZ1frZ8|#m1J^HoE!Q^mv_qECHddo?pglP zobuhQ=$PYVshZ<|!k6(sjQ?YjT{nNO{}uh{T<`Q%q9qT-w6*4ma&9zwz;b18hiJBk zz)VGs&st$?PnsS%vng(MmMqsgzS{ls9gkSNm+MQq=-Cb>_88TqZ2l?%}&(U|qkiZQ3KZ$uk%#yQG6BS^FeZ1g%~%``68_D~}3=N9^7k ztQE|$EJHWL$lX}(;*5izd%la!lNXrEbK}G-`$vs?cecN4JNSkD)2j0PqDSu^zBA`q zeO33RH+O7ssrU0!{>c^%4$T(FkInJdwCHJI-)2|yZ)?=1J$E+z-M8i3o!H!c7ruPm z)fVn>?3rt^*{fAsH;LS?>HL>3(-6XxJmC>nhjB0Skq3<&Lf?xoytA8qZByY*VJ-K4 zTGO9GGJ(tzlg!}8_4kNcXJ z+e=MvkLA44^J#hA`+H$$x2D8KP3W5S$<3U%I#xfO4`!I0Qcz);5h2@X`$zbM`@Qs7Cuxayu}_j$@)lY?o1x)Q_Tl(* z=j~l_CP(;VwHP$7G`6U)3R(&p**sz^(UF~=v+ae+sxQe6c5x=oH&!jrT#{0emsuMn zuxi4=j7i(tSN^#q6nI-+VM!yqp#0uGJJlQJiIV61j$8BR-O6Fa)*<_+y=((G>pPqjm=e?!>w@$fDo4v4y z>{XBJCVbky?h2wOiTtHX#2ZzYxC+20?OKzJy#d}GXB-J z`>$>OZ{N_^Qs>=HdlSXkx)vC4HER2BD-=rNv-_m|RQFo=&g7m4<$eDdbVAp6{LxEo zc_q~$^Ref5_^Q84cjnxFpLbh-n|r3V=KNGq-j!RMCMzF2_^7V>k+N^lEw!ADER5&v z?4-ka9!>Mv^vyXZWcHNPnOV8VE(pjR2%Pd=g3EH^?}WMuihEK|B|JXR^Zdf4FAh7^ zmumf*yuto4x58o`m8)itVx6vt1fTIwp2KOEB2-s8twlpIy!i2nDlWdaJk3=hCCj=V zZ+y4icW+JNv70Mb=x_SZAb8X@_~+EW4gwA;M;;u>HaB+Ge3Q!Xc>UX1*IT{j7qduh zUSn-@r%;ApWOb+gzT_|GUoJHd(lHW|&~iJprpv)`o9D-cwWs&$%ubvbSi0id4hz*S zOkp1t4ha4gsQLD7{@jW^UJSi#{&w?YUsV3+a9rfLRn1&I;#Xm9X4u;Ed29Yiw|rRf z#d4dG?U5yk4r~02jBlR~+t%aW#*%(ssinWm<-v1ix$1wSrW>oe^B1mLv@!G@TRCg% zlcgr&3?G9VW++bI$5R(NXClMFYkeD5Jq!>3cW1?KKSgO7vq$qYzg$~(>D9r8xV{3$ z$|@F|MuIp>(;8A&{wm!I;GvD6+3;Nmy`dp ztu2x=UK>xy9W!SC{$4;i&@Q znTdJYJ^Te;Y{_bcH?rr{xPCl4pF8hR`6YhallJY;W91*NZ(6qBDI~PwVx2?Y;_BORUbmxg<#XbDVlSAb+dA5CeF?p_k zE0avn1@VBuW@FpM6FK~b&&^qw@ii!9CBp=!PhKv~ zw@;jZZ@b0HyG_L~=hIn_bIz=BTR%Ks{$lIf6DlVrsR-GZOteZ{v3}>xUrE~xS4e+e zak}|r;`S`LSZg2kJ7G&7IGnU!BKZ52N@Mq%;>b*cLa(z&uli0+Tr9e^{B+gJol{hO zSLn%{VP%|PyX3hFv(%BmLo1ia9(;NFb+l4@;nHoUNBnlwi{45p?Jv&F-Ja>2?;}-f z(i=OeqtsO6Nqa1>gI2;C^^QLmW`3566+g1-@{EaYmMQa3e_z+0|JPo~@mp`~!!zNk z=FhJl`&_(Y_Tz&4@7ODD+^u{Xe936TZBMyJ_xY15mfvDD2=SPy(qy!GO}NxS9)9N& zzp8?F+DUEwt>qE2^7HGMzkA;QJ$TjX*Xp(Z9_DB5yfyVij^ehPf}9?LHbFD|it6J& z-rMB3D&t^46<@qoD({vew~cxnueZOC&^EhlvhJU4*DLAOM>*V=`t9H}oarga!hHMf zf(MTUJZ2Uh_{mdO;<88E?QEOe{JlIe@663bcGqs3vF!!>RnCbgcu@Yprw z+ozUJ`7!_2!bim{{W})anet~AefA9a{GG34=^~aLwi};SR~o8IGW)1*-8lElp9Ql`Cl%`?FiAmKCMMTRWo?iq(lW3{+1V!Q%hd@ zPAOTK-spO4hqvGZw=4XmRSVmn^*s)Lv1yVNtBVNp2ZLnwFMs(uw+E_3*5qF5+P!B2 zuS%_b75B=Z+tnWz_8;A~wIJ$l$SMv6_xZN}lKqq}Tzqj-?sv^f8=cVIo)XEYt15S2 z+WW*RbvssMJ(KPJ*cX_J8ACYH^IGCYg!o$)zl4GKJMKt zDS1AvX8$}pJ5htj0g_Q1>MZMwmd`l9Jw|@5?3dTC`YXQNUL@=Dt9C=lSDS${qnQ8%;JqttuBU8&o)V)y0!qn*xg();#$^{ZXBTlXJ& zSL?Xx+OFxBU5nR#Ikaxtv8)NxZ+SID`>8Tm9<<@R6ni8%xZLcSy}ev?vcz+5y&Wcg z$KHoU$n9=$t9x;d%~9dURi(70PVc>cuw})q{d&7nD@922MBtn11OFNJOyw9}=` z3AVqkMhcp&*cow$#Mp)?^8uoW9H1aznxWo@OiQk z+v|Br^QUK>j=Ou)&ul_qw4YSRgw`vQ!j?T%-oPN=p!_^Eqe5`vVRuEl$Yb_Vw>xKg z=PYKMY+`=v_1RfZJ#)8ST6-jfb)i57>+yt{txhR@Hzq#jNqKl}!oo~Wi`R4H=W0y4 zD$Kcltp}f}*Aym(9EZ|Fj`C9~ zmA!N-%6TPbO1wJfd3EEbgCgul_QY>$ezTzIZuZse_o}`vmzQcq+MHlneb9*im7j{+ zn$58-wNZWR=9ZbBUfLUz%689m!6aJ^&Wx4m6N3CW41XreHoh_P+cHV*L~)N?^gE5% zEK7Z@?y3bEx(n`$P1<;PQkNIcPs^Dnk2E;4N9i2B?W|W@wm8XQ@53`!wEMPnM2Vd! zk#Rh=T!20CHUs~bSBXq6*6syT3!CoQT@DVBjh+*9Q!Ch8U6W<<1cxC11cTBiQyZ?Y zV9L@|+J1Gq%2!!fddf})&OGQ>f5!dW+m&l`W73=s@bg>=wpyWP-1w;K3TKI? zr}tD2uM-SYIu#rQ=X?{qFL`Wnn$=zDqW7 z1k5RKd@Pjl$nda+*gJ`eqF)!TmmPCqS?9l0wcPH&t!TBWyZ#=o-g9N?ap&C2tJd-= zW-B;xH)=LEr5rE5FSTKw+Cjbu<5 zBdXT_%v;>~-R@DNO%a2-@okk1>pG=Ri``x=mg$i0uiK(}<)RVmv6^#NKbLN}^d&y$ zo%g=0cecy!F8w2Qr|hoZmTN)5YZ<*B&5vZQyPBYq(&3+AlELtwp|U}JqRNBnOPx*! zTCe9^=6-p*%y3W8s<(%x>q-BcHFbL2>VlP7mCFMaHW^Jj8K4m)Ea335S83YEz$M&m z(s_)sna9~mD|QDN-P@X~7A=N>=4brw(!CD)c?`V5<3ZZbISe4&60zuO~7~+MnIG#hD-Im@`Mhwt+0n4qMYHDJ^gAWC zlp&;|WAZyKH5CSqc|U%?s^hc~Y~!0G>|Et^{HwEXad&mUK>=i5>-L#Xn{=fB!(?GN97 zZE0(@KdQ=h-+SH5xx20l-Cg)y%)$A>Bx&QHli$Am{8)H}UVgG*n)#c{Im`{k?_W>*p^HFEM9R{%}Kg+TXb-m09Tj0k^{pJ$D9PC+7zM7DJ~H3-}Esr>od z({ES$IBy-#jaY@;6X`!Pn(aKwZ2oBN`s)y_w^h3K^wo{~qn5T*Wu|I9KX9V6L(S9n z5f4vK#&3bi4DU+b?l@ujvuMelLKWqlo=?Zje_rdm|EuWs`dgZF_QrnKefH}yhpDMv z4425m2lfg+ikwEtZ|3+scCMDP4Dng<`QFwzo=XmF<=@WgRyBUPUB6&S?BS%i)%R)@ zU3sopIA0L933%l2#8B?#@9)=+cW%A-CbG8CzE^G6!(xY`C6_-hs&%QZ$Xy!!?nS8F z>jn00JB_voEZAtU!^oKVO!?ae@w;h^cNqGQtuqt9*^nLk^8U-E1q-CTY&R|D`Yupp z!*TrkU%%=5c{A(x9j@E4e8Q5v+cJz1mrn>WNj_nC!zuf(&e~z#e};@J=7QN0j@##D zES%Uu!jO-g@m}(g1TeI`6v(=?TZ2vQ)z14l_ z@t5uChRE;Cer8j4-1X*+H+g>0|Etx6)OFkELTa0okKz_p1qKES8HbN^lWyJO&~2ULWy08arhh4SVYRY% z`}@i(mz;#As_I={>mbq>{7(KKC!0ZavTxW;qn$PKLcGj{(#(%)eullyW1Y_J={@15 zO_0J0#wYF__CHEH#iL(No<2SP^1Wr-cvobJHtgExnxnB+#pm1nk`r zm8;#YAFj;{b&&|Q`IOzgyJ>%U?8P1Cxwm?sXwAH06~M)jsvWa*lP9YelZu+3%G|JJ z4-5C6TDpTndUi_w=k-f2EGc_n^km}3gTJmOtlfJ}^;FlaXIC_t{yN-@V0~%)UQWoc zzd=20_H1nn8P_}`cQfnfmk+IoarZWil=%HGWc#*z{6D%^FLRPy9Kcm5)8WN(BJu-E zgZlQ`2&ISr88UiWgZp#1Jd+L?mCW6f(R6#4c+|8TTuvF2maM*2`=|btpHZ5_`&C!w z2r6|hkz8$h(+83%q|xjOx0p5nCGU(_!Zch5cL!(U)md|dwR^E*k_5s$ScSaSDiG;+PZhAQ;%-+_lXN$v(#ozrI&*Zll{s7AvT}P($*f|6YDf?e^+f= zbY;)-y&)SeU-1YGy7p$vgFp8LWm_Km%x`y@`IgUimC4?+{=lbCd?Rf8udKaR<@M~~ zzLr*@_=YfvEd^2@LlUtK!CDCqvXDW?mUF5K^_YyGm-EqOu%BTJJ~ z%CRL6?!1Iy;_hSboNdul!(BbLuZnZeA z^7X46JRINo%NYyH-Ji={nxZfmwI))5bfp8y~T^-OajqI3e?H_uqR{p8X2{<+<|p z{D3ljpR%y~Yx0Y_7CpW8*6VkE^zALx_FqGJSFj5`5?QM1qSM;P>ESsw^Te+0`?ott z`y8KP-TbmE@?-25v%)ODdH4m55n?YODO0NmB%-s2(|?-m-&_@q9h={`r?9@Lta^S^KH8fBL?4(XXf8p6g-LP^NLri znkrVF%T@IXY*>?)#(u(r`I()v>v4hp{|pAqw@o{B@66qK;rDyC6JA|G7A>uUeaF=w z)YmIVNr=whT*E4}ZK3XmJ;$=R7ECn>d%h~Vw^{|yCbmpnspY@5#=Ymq;~U@lzh1czr|GoL{L`P0Q3^Z@!)L5bRn5|jo;SNQ zz`{{M;e_xp^*#S~-Oc$rL4t8U^Y;Bwt9poZ@Lq;@IQlK28-FWdn{8_?N7hi zP~^yvqE%FKV8+9QjePIdKlgsJ=Ncaa`;p`irs3PQ=Iyn*JMs9cxK*o4_z!L3TBR*| zLR5L5P~k~8yOfj{r!B8o+brIF`TW;ab5H6VGfkWvc6@QI;fh-!Q_H>e~MWm^C0NnxX*$0ZL_*~S+Ib}n_L2@hlJEH1OZue#c>-R$#Hmd`;u zdic%>3*X+K*uQUDyNZAx|G7EN%!bD@wzf@q6qC_*YKp_jsWJ`{eF~iCCzV>e8#hEV zHHJtP+&r)4U7P;yRP4%&)^XRopNd}p^<~q`T&?hbQy0Vr8f-|`V2thTJhn3aiu7mE zZnp;Uyof)qo_^4YO3!3^&My7wY*E$PRpud=rhEN9^?=>f;?DYgi4If#wM_o|^-%64 zH-~elmLK+5w^#oNi%h27omG44teP4HesE4c!CF71|1|Ta^xNvf^FEbc|IyCVn|nln zY58GMw;gY0+fMiA3vHjfYAg3jQC96Be$ihE^Ma3P2q+pKf8Y2#@Pi*)Qrb*s_h)={ z2~W7z$Ea-gp1dS!diA+&o96OfHvM7M&>%8t;)$KgcjkB{e4lT4!{Oq^_|BNdC7Zq; zJiqV9ufQ1Pwiu66HU@saE&Lffw#EkWCax~kRN3YAd!@R}ZjCUdF=8nBeE;FlKSh&T=LHL1ybsRz*}5m=wpZq=S2CP`sxvh` zIGI&5**H7wS6kK7^^S(b0SQ5NaBQw<_`|%cad7#9htcx^#m8w;Lsp{H>g@#W#?XF7w3Yi{ycACIuPMagg_!v&uPpO-EMLfJgbc)L= zcEkB6E@wMW`}gSmwGSCeKq6m%F!q;U{*^%BU4{^+u2z}&vfOT`rV?6QzsU-e$}I;YgLb^h@4tzcV%Yh)7uAF9N$}A ze9oLPqd)%IvZffbsrU9xd%>v1%D5qvG5)gpmPvN@+uF=s`7@4N{HwmWu`R&!Y5dw< zO^-c29G3?r@G*CJ?XFK|^LZ`1dGqUI_w|n}?eSnX)jC_hQFs4~)|HOzla?rS{<&v; z{I7b1xW53$$t8dOGw_5joaD6Qc=@qyx7K(~x#_)LX9cTLQ0;4l&hx*8toeK0OS~=g z%6|rmoxG*jv7LEs&h7x_JV|Y@et{jQ7V!wq-y%O(V7imqf%_&tmyI^9lv(}cimKz4 zeV4UZgeEy~22NC%e5-K&w$0zxGORk<_M%4H<5BCUH|0zGWAk&O%1Zw|`54_`;uya4 zZ0rJw=G*prUTr*<&b2=ya?|5=w`(<@HX7~lIF^0J{wnXb#g*%>9Nk&6Z>wqfBZ12c zrmR)9oa6b(c>byM2d@^*343{C<0bab>-CqG#J4B^wzfK9em3y#?X7pWN37evyH%+E z@XdlvA3V6c!WMXW_I;MO-5<-%dt|#fw?u;Q%5~c-k6hGc)a~I5b$nNLWq;|~wQE-R z?wgx6W!sd=M?F~*61vM%xdb5&|Ad#}BW2(lg_HJeQh;qtoj1_dlM^x@ur% z`|ExBiluvky2>Bwgqh8c-SWfhkf+bbDD|G6nfB7$iA8O$$L8zRYXxR2yK25?s?nHr za_8%{eJejLVGDQ=WWisYW_Y8Vq3_rF^I4lbA}gs{f&v%HgH)23{j zV#&gnP+>f$T)ueK#)mtKjkz|t9$V6OH}}B(wQM`4TAcOPFZ!D5>9fo}sIGe6<$trP zruA(6yKvjrRd2rwUpgo~SLn?;{cBpS9405S|7GnpSm)xt%~;L7UjDYj$8!pYF3e%# zvzOZTM?B|}pR~z0Tc=m-J~`3vK5keqdfRW^>y#C@r*o|m->VvSJIh`CW!M7sON9!o z%Ti;CN+Tc0L*ENzmJm6)!@5^nvao2BtJ9YnKVp-^yTbJ_IpS8%@uxhKM$N{Hj2mbP1 zoW`r(o=)@CSiSsS|Kv?m0wq0NI`u#5HfLyKCm6~cX z1@lfa`M+J+`=)bh;HEz-xJ487q-TCzmUeMlaKPQnYoB~mC+aP^vcuDRsxRvk$2T0T z>Vk6ve>@kxewyJw!}hF=a^i*(d2x0kmDg`CU0fIwa_#2rYrjR8ty-8r#bmO`n-wZ% zXJ$STV32K8bxRlA&Nl6o{h#dnA5Yr1X{tW?JNcDiw*A@HZ13)cy<4!+e@}Vn^rYL{ zd2P4OPTuk@TiI+?XxlB_uJwNNn`|q*D&_1xsr0-$c*J5&kBjmy8^-t5mu^c)G<6g{ z_55Zw_jl2r%h#6Je3kjGEdF+7^!psgLQz~nPws`jzfn?Gtiy7F*t?7nww z_imn*PWsP~J!2E!tH;xyyPEUO={j{ct?=S7RfBZj# zm}}7^>%>i;KCj!j%}MBm@&ttyeFhv&F8p)KtTyg8$-X)H&Wr3>8!P>mUhX>c)aj|7 z@SlsCtS;5X#-I2nu;;uM^m@fu=qgfVwl;lLW$~k>G85)_20op?|L?~|Q%RN_5`}RaM@mD-#wNbG>D$asPg7akg4N&z=6gp3JM6*o>bBi26Tc2I20vvtr8-&=l%ni|)=(pbYb;Y8IC;1G0WoKUgZFh0$Dqr2;C6}R@@{hC?#a_dy3m)pzEGK;CrNdMXX$5GWkR9A-M>5&~%M9#>&E8j9b zxX=Dkf3b1&;t2scmRVo&_G|sx-1wit@1A`7;(hu}&(&66mHTivlvgNcTS$w>Hs*I5 zS{n>h4fYu+959^Y&|CEL%o@j8OmF+Lcl`WusdeM#{|uJqE2OvG*S%sOU4K#4lTW>q zlSQ3V;sgtijQgCq<=UDm(UZc0+vE#AiJnPhXWwd}dudDN+MboBCh8XwzfD^2GnKVf zW0?_~|CGNUf4yAKKUeQzd2dPMgu}@zehKlPkP5Fl_bIc<_E*vO*WR&5jyj7v%{(fy zfyvo*Q^>S4<>%CI6|+1KIZH7_bv1xgx zrTS+39X*1A1Q!1}bDnupl4JNC0rfq$KkBsSopNZAjpdNAkNNazncGJxN9lD-W!Z{q zt7&bxb6!tjyJx69ESYFdc}qS6V5`)XDj?A^U*!p7qh z|1(IPPQMdUU1OiSV@-g@niVrOb1%6_dc5=OndEtl!9{*?V6!;{BOF zuHKCQ=DGZwH{<2wc-I=<13`8Y8(HtXcQDKT`a3aib=||;SEq_)KUJRJxqL{s}% zZO^fNTef8JD_!FZzN!fmBPKL7JWhGA%9Ew$?2`7&pryeM4<@o|+A&PD@i}K?_;L9Y%l`}~S8VTGXPssE zcX2>hrk2Ytv85UgPuBSG9B(Rqyg%bix6K0n_sbSX76=!fTR83Vs-W3=(?u3JxJWYZ z7p?sGoAdhG$FquWJV&konA1-Q!+)elBleu}p@z zB+L7!{amS>;Z6}!m)7xwT(b1r@}EKHl%NII#CtAGjLQFJUW{F`ilzHML-(OAJuOZS ztDFm#?D+D%^jfgNqm`L=>aQ)S@N#@Qsn4N9$+7ci<6N7r7gPRySoOy1g7=!Qt7|uw zJJ0Sq>>nQ_)ZVXR`}R@D2FCvkw-^OdI40W*%z8RQa{}Y?^K)Z#^VoXT*GlYq?H$13 z5tJgMuFH8KJmGR<^2e>{1{M8ma*v#jp$>(n1jy89>F{&ACq!mq|n zQ#m@H8h2W#KYzP+-Qu^BDS7ke{3`vp-cws~q3!vMMR&ryr>hl{^&li62tNOG2#kiOO8ko_KetvBwEjJay+TD{t9uYG9Eo|E!S zY5eEhliXIuXg`=;9r$ux7Yd|5t+zelHYQP`;;pMG6u+BJ1!^cLG4 zFYYZ{vuwx01e2*vQ&l>&SOt|I3*M_q`_FKoV(Z4*jH5FRC%8T8o1(Hr!S??0ol9ij zXDEu?u6!O}{>S%H(Eb4C6F#krB?1-x?!O%_clK^i>&Mmg{!KT*duI~FjH7WgS zX!aNH%wzp>4y)>Q90Qb(r9Z#Da{1P)J11C*26m;qEuYk>GrOzl)`vt>Jq>e>pey?n z6Zm(Ye^tM)XP0%hpQ4~}2^Acy?)j2OuuzdZqa_^Q|X?LB2L%im#C{%RmXGmw>KY3mG$I?w6 z_1o_-FFf|2K{+<~fwaojN|kfRI?DZ?Ec2ZyCerq5YDmKiuOR;=Gft>0tg7gr8*Y)$ zQaZ)ML9R6QM|fE=t?jJUEFLK_xWq*5A!2Tnb z)$jU=gm_u{8*m;oy}Cwa>JvdXFSO` z{-qzIUoTNS78G1jlB+(3-GdtGI(! zfb0e32lDnyWW#cQSnaa*-toMLb*-M>-T8jo;;km!oBr)pWZqoO!b{dI3a2v#7-aV+ z|9X)pm@d~=8u#Jp+}DL=>*lsZ&D@ij@wM&A&)9YMuGT1~`YrO137z=({QU2iJ$7u# znss-BujRuz(iMl~LK9`uW-tB{9rd4KYq{8?y~ZJuiB8)l?ke3Fc_=HI^Vx=F0bxur zwhty5u+4~gS!sHLucw5$-mdcYc~NRb)f7!KL-p^L_RsEI!3^5vJUV5Q# zZp%5=zS=HtI_1g~-|FyS4n-}S>zpxtE{~-a-}5a$!hSP%xlY3zw%c-c@mkqOmu`7z zqpRe4@>tpPH`$l<9shOilD;^rI`&w9_U-R|Uo$zL*6-S)l_g!FA+&7L`cxYMhDS^b zIE~c()>ya%tUAh_Rk!5pe}=9H^#Xrfi>nr<*1dPIx0l-0p_M7Kl*2%RcZg;fO)!AHJ$1cwGSP%T`rJPH0OK2RomxnF&bC9gi^1} zT$6hE=marOmyV##-&HD4+Obz$6gM(!4ZQp}`eLDhvgcZ__MiagQy%WA8fA9;+uz!6 zua*5~5a4k*z>b;u`&#vjJ!y+pt@^7jw(OEnWufuAc|U&3{AXBFDK+ob<&BApjEVzp z-7U*1KD8irRi8pv>q>8yZ;xs|$+>XKueh*EBF!}I{xbI05uGz<2jytHUJqM*q*b@Y zDP)3?!jXo;2A|sg4ncY0J((dcWyiNaoYk?d({$d}RS&%8s#%&QbMq{-r6izZc(~xq5rf-wn6pKe`^7B&#Oq?57kT(sMwC!Oy2A zv$u8kO5NqoeK8xK|2e9^b7}~SbI9Sbk&7=Gm=^rdJZgDumC44lc~S`iMO!`wKAPmSM95lwr+wZ1_bace91P<6 z$s_!(*x`=*O162Se`b|4IkG4)a&X41kYL~`le_nyVck}bROWzV?tOOq_onK8)thr= zud3DUXs;lDQ8TZO6D)E^(rP4sOT1l_w#&mI<@fXXZv)sTZ{e`y_tH@gt@E-tf5UGFzwo}uC)pd#w#Yx-koff6ezzaj8Me=E zHJ$WEgn`e7)hD#n^{CxbXR}=?zmDu!vUZDh=cE~)XF5E7eEv24j?}Tp+{3pVZt?$a z`&aY&$?Dl*?i%kzdN+1$;ke`|`E%%wbolbFZdU9oo0^chdz&&oesF{NKQ+h^YFD z+*DrqNfw)wGcKR7&hPl)=g|eTmrnd}dh+9dDJK`Nskoq++d6I9R5@2q=TxQWXC6~0 zTFMC}B>Sve^6%5_^)=5`wiUT=yP7=h;o`iDZvNBmx2jrohCJAHEi$(=YuZc|1#`yC zsSNx|#`il9Dhu|^4Y4@3oM)qp$hx`@H-1TNuQ9I5d+s{jLcMVE*6hIBD_82S37qfA zz;>TCRbs$|CW;tOhNq5)cgydr0=NDnY6xkYn9ymt-i&{(ziF}{`tOO?cCkw z?ta@8XPy0)YyPdg#Wmp(t3?iPd9-P+V^(3JQAkL-q56R-^Dl9p zW1HigwDY7>@%g&AsVA3lt)3jvcXcV}muU^t+3aUdQr{54&L%3quFmvj?r%0BhxWa5 zPd@$0b&u0i@@1gj6%~=`bG|Kg|FuN!?!F)Lua|b;X605&biC2~%2Usj*YK^=^eO8e z=dD@#CpVOTb63d82TOz>%U|(JC_ixI<>}qZZF89_Qr^_A(rlSkFf}-T_VwbZyi)7< zaxU+~8lJ)Vp++LN*UprmA;2_wLVaw6(i@2^H_I>VAC`!VFI^(wy_s*zD|lz*m3EkPR_n*G!(SO%@vUNDntV&vy6E1Lnq56%yQj0*2|eU; z3O}Oh=&)p_CHwij$5Mv6k=CDh|NIR3Xw6q*9vd+|V9`U9cjr|_TW0>7r|@_4B9&Ut zDHdvrCnxpKHoBHKrsizBz)x@W1UZ%-YoN?EKf7RhW zKWf%|nD98v#!)oZT-Eky-oodCfq_1X8FI_FIG#Mm|Esd^R_Wo0*E-F`kE1@eUAT4A zvO@T0NX4QZaj!F_mOr>|(&02gL2#1)`E%As>eedFO+4ks=HDS2XL#V^w~pP{dSfSs z?K-+4+g>>0X1m}K{*q@Y2j3aFZJMx?HPWwUe^jJ~k*wigtFYPnx~E#Jw(41jRJ52) zao=jmXgHsZKY7uDZK~}#T#ZjBJDj+#@*?g-T*;L!cUQ$OHF=TwOl%3mRIyJY^B%gC zo#@}+c;CaK|8Rh(;$)q}$C*E@oBZKFgY1zs;|a$P?Gw%2dp+>Vp1V==br#Drf%cf&p}O(m~Iwa2PG z`nO~qOzXKYSFqvm`Y7hELh0@%ffT`K8HYAbd-Y1-sXJqe9Gq?;Q05; zl}Ath-;H|P7nbiD0#;ADC-LXSw%ZQxMgM93XK)dn=Vvm{lcDzaGwXE6*e##D+a&_e zJpQsQ;BJe9>hq_we(sp?_fg}acCCy#3e#u1G;~^iRH>O^sr-IL+T660>MXgFDu0I< z*_Eg2*cL|QThE=%`gB*9_@w30rzBQnYI%iB@>$EitLDfYj$YG;8@&bJ&Ed~K)z-&0 z^{bu3SLN5fe}mQD6lJX~U3C;D3XMevK)`L$YKPAD^;NBjNfPrDx93Ger;)OcTex6L^G z%JQabQ&g|2H0|?bjA5%eU%$V8Uwd_U&9T`Rl+=Q!-`gL>?O5E*n71IwqOYjHsK|Ow zn%lOm4<{-AXUHx4tnkmYd&7~`%4?##rtIKUvc0f%v4%q5A^yDQZ>=VU9=PE7=Y?8n zuaoTAOuLUxm2B4@r8G1!KI}XGq`EF4%j3aw`n7qvLHM)SzZ9sMU1-L>SZC)y?WKa*1O zoSbntV*Ny=?FAB<9se0@U%TvRYU_wPA>kFo$YrncN5aB(f2{8^lZ1nXuZq8}jqzB# zdvD^Gev>KFPI)mX{C(bkr*r=PzFXaII$}0j^X)USIrB-zTrtYO{Z61aqej6KCH6g! z9GME)l>hybd_K8SId)(A>!?_dpYQY>o_hQiwc?L3@7!g4Y^m8TV>gaJYlIvXJSLxE za8N$-{o)3fPwuzuO7-=nf;l6Xsy#@WdHw9lv^!UFFU7vNxhf)P$sElcK8Y^h!*d>M z9(lQR@(KP7f4iy%)G;Iwq7AcZl27O5*}4o zHu?W772R@DG9~5Ie4Scf<;m@>?-}$O%u;`tn@R1t6Sxdu~&H7Z*Bj4C;_RF1HmJZQW8aX`#&7oXm{!B;bm&y1h- zKHB8%{D8H*OMMwEnLAbZF5W9{{`~#Qwc;ZYBDs;Dj-UU&_VJ8kMzaG;x80j+vwW|) z*j9hVhbn1>xBoL7>$5Rdo!jTiYttYb@!VbPb#bX^Zt#{{VYh->y7Y3^0IrT z*@Q)S-nNwfVv?QdWa}R1Bp%W4>*c-o${v4VwQ7!;Gv+j|zAyRUf~Q8YCiCCqg!*|? zPvt)2@Y?XcvM1ohiZ9u-O%p@;R!<0LWodrumMlE+li|b_%e$^@a{bnPtFMCPNyW6M zr%x@AaXg?}VAFT_!`fr7Vt1HFI<0EIr;$B5xyv(ff~TP{3!}Wk_qk5GdOW!zIWf#v zQsiq_%+6kxAR1t3f38F=>)sa4=c_X{wPoU%=C-tTHYwR0;rRETp;~qB*)su~&fitZ zZ9IK%bH$~JH%{z|_;l>csypFQzm{4o{b&%hVcLPI%M4k5o_zc5z{OATv$wQ(E{!Nw zo?ZR#%CS>d-_91Qebuq_ZmGzb{|qm@d99cx%y3ZjR^pt%z*7BBjpxV3>w8>PYN9u= zx^aB`d^X~7THI6Tkl^hRmtRNwhuX3HXE5LCFC==>CCSlt^9{*M|MaF!`}Sm4yRDJ` zz8fDG=~`@Z)LxSETISoQ7a3MBb8FkhRUYtE#{kY4dM(z}oGl@3?EP^2E%~yvBXu`-%x-j>^9#IR>$B zv-aDVn|fF;^tR3#yLWsJU!TrDwQFws*Hi!QvY1c&HczGGi~UyV*lij61Gg7$|0kJM z(9q7<5#Hx~-~juX^qAjWoQb>Mq$aTY@V>e&{>4cpZ2m_{`vo|n3k!ecl_YCKV~a-Oh2+myEEFvoW&ro^hN)R z;vYT#8IDbv_HSNkWJp=8o&D*>w4DyF1oZqXj0H5EOEgi{P;kMCV|E@}qjP?|n`zp3;c6D^- z?VMlXZaX#GJz17aIlyK3GtphB-mtmXsJllralyo=e?+4nm8H47m408DAJVnC;9cL` zn9Kg9i&uL4TdY$|ZcgC;mlP_%p5b}0v)5?W-+7^($0k)3{p9_(Y{EhFq?-v&^Q97E zt9mb=z3rZS@A~bZ3B_Qf?s`ARUsfoJ3G8v`{tIHR82(J}lzx(xjRpY+GH=1t@ zk8#Yc3zgipZ%NMd@0ly#-)1klrTKE+gVXbNF~4NB44Np%X7PN-WWj}x%KkH)Sk8G_ ze#VOTKei-fr5&r;>^?he-o;(FBW};$+dj`JXlrncW@CAq&!kCQN9G82oGW>{W0w0O zOY1XM6&H(cd7R?v?GOAF+@1K=>+jO)mHY3mJF%)ZciLpX1CJQgxTdOkH6E++n;2gx zav*3%^7F}`mQO1@%RN_jzOvu`ADz?gmS#nGu{jt7%d7vbf4y`Ov)nfBiQ8^|T5NZ1 z=9SxF?D~xwLGwJKySyrygzqUFIQj9;kDpikCZ;H`O#ZS`d56@d%CN5Q%qdotTedc( zEa8seP+fORRhUI-ne_8$M?=P$d|5_|3lwyZi#!ULcc)sY=0OTm{pWKF=Bm3N zmg(ox*s>(uV#U>XQM(7X=4iHj`uM2uM(2Nq+}zxpTh_O$FI+NN7BuszbLjRIs}rT~ zR8yuhpHShKedK7Dyr)-ggQ4u2EdM^nZ zzNX-Ln@xPm$M2l7SgXHqy5aZRXP*2H?QW^@E@n2Hlknwf%-dJ}myZ9DS+!;1t=Fd* zzKCq)e9%yMX0hCO*k?veg9lt%m)NI+v^LICNEa&8_p1_scrJhx#p8EHf!+ zx#NMJZ?a2jUY%4I=z1h~QHAvn`|F5Gfo&Z(L}k7|b(n5leE#~5t4^#=AuGSKG&Ly* zsC>V;O+06nfb8G;tmck`MrW5PPR*a1ynNgCEdI5oq8%%P3z{As?D?1Xx25mby*hQn z0?Ce}Ue|n?lH-iOtli~(R9`Df>)Vahx%<4IpYBwdY*p6 zF17xXYQ3^hD=?=>oxS77U;96=qjy&-IoN$$FQw->&*4(Who?Rh_vVzH-6OTEuxs}0 zf?JwTxhJugI({hr)f=YpaC=(bzn8CjYBi@W`Lt+i+A+J5^b4y?`JUeEC@#wVt@k=< z;qrQ6=fa$wKCigFykc!U0-{|rHH{gboX^cYs`rFPY1@q-U3QfdatdyK_^Z9_KZ94j z=c!lcLv()U=id;_x>2I2cW-@;s``gFtnSNhp8p(vg3tEBIm0C}TBn{>8oYXZw$1&q zx4y-RrW-RO#9#3Hdwtn@>0IlV^d*_8|K^sQPI;SkZ1$J42UnTQn$)0tH(;ONF_kAj zXHGVI5oTSkFLw6&?(h3{hui*VSaT;vEWN1q+aImt8v|r=M3-pysXRHRe0S!&{|s*h z7cHNvCmqET5@?mlxO3K|%tKe?J^4g9xjYyIWzT#Qn5gn6{oLX${ky#R-lloTvpbv= zmG&>mJU(}ECjTezM)yf+hD@CAZy$gCae?kNjvKE;d5`C8OzjYzc|2&TjqH;)cKytV zCrw7`Z{N(2&zx(VcXYaa+!mqM-mml0m%8nF#V+D>$;&~}i<9GnvEhT69~)|TIdxVn z$UMpZcgOL4zFFUuBg3qJg)Mbo${jSJa)m&pz-EPVixcmRtley0r@!o(Q~Edi$jhK; zcAGufOp`o3bX%SJ{~Xj*NlU0a|2w({`vXa+7Lz7T}_uRZJe{3 zLrpEL@X3$&j{|Me%uh&H<^~-%=`v!iYuOfbdqv6`)zliNv_cmDnaA(3-#1D#t+mwI zC$TVLa)eU*`|GoOH>a=P9dqq**sHWFpSvD}1vE_Z>OU~S&-k%L+qQ3?&K+WT*FV)Y zzlqy>!_sx9UiF=QCF^_qpG@%lMX<(`7mKga_*z!{PROauE z`8H|&LhClfHJXb@==fYS&)N_coTt5O$D_HKy1_mFQq@_5>UECaack~VN-5K+zH6EM zacQ~usXpsHe?I%Jb`3W7%C0@}t2g+H*@6ih6b<_=9#5A4cdUb3r${Gx;>Y_U=QJHB z7(A)^5x7@YYKm<9f~azN35!oIlRG{dT25ZuH1CS=7tx(XuOj&U`ET`UtPHs{<+SgD zNh>%w{~SeGt zHDk@XD@qwpbxrEf4hn6!o%!T-yTC1f0tHlBHT z$&oUz;u-jAzb%$pdv^P)+}uk6mkoY}Y-Nkf+!r%hiN!5J(q^}VavRkG^&u`6R^4xft=x;3Y&Z{?L%3+eEj2_ord zj`0*q{C?3b$5^a$C?oCUlLE%0Lf0P|aBq7)HRtB4RhkPIhHcmK2oi{`nDRq?qFU%P z&bRm7-4Ev}D#j%}c%H*AKiBBticid%6Br*}xsrP`Pq$qpZl(A9DJr@as!Af8`~`1J za!aW_x1dg<#3**NS|uy9+>?JHF+zHh7dyYabl6@MqB!NM$>&mT`ID-8<>!+o-&qzq zC*5$VbJNtvn*3U+lGoMU(a+8n1(Z!y)a0toQuFC^ zk?>>eIDf8iHM{VSkQnv6mThdFHv~T`n@qXqp08hFUjFmiyzAbns@}Rg+9Ll*w`ffG z$;aa`iN(>lqmkix$UMd5%DhABQo+;iSKpgqE@Z_#!NIn0{#vHZY3sLzOn-aTbj`j? zmp-TJeTaxkSkkKM#L-ZlqYL-!$VI4YTw(})oYa|y{g|9U$^s4ctG7WyPbMr zjINt^O)XwsH&eqo=IU%S9dF2+{{!dxa05Z z$uY_Y6Ybk_-`{Zl%$b*3XN%NRbvWZH@BFRT(#qsx zWLp%wTj>6Zed}^zOH`$^gn~C=+=E5AI$CQxANUx`65q4 z_s$AU?F&nmYI}EhwI_43@!7}Ebu6v5PuMk^WuCLe_mAJ#OB)=I>Ef^xnR@p(b52l@ zY01Wjw^z1T`TKqov$y03P+?#>zQw1!|Hk>!n&i04^L0=2Fh65Ff2wuI!;8tu`&&hJ z`fs)BOgwv$UT#x2^A2`FbKBr@zf`E1B6(P>3Sl27_1=#9pUbfEd zPrcu}ebK+=d9Ru3vZ)Dyww~E!!$LQeZmp?CtW#3rIXE>{`yvRD>L3BPt%A^IB zE;1>?jq`5&yfD3Z_sI!Hwye^t_lfShVtu<~>&qoOriCghYKD9|a#B85ywb*sLCY;e zW``3?^uAOcouZ*WX-Wv2 zMFZ3R3nv8aEw6-6H&XF7v-|n-`|66fY>!DXRB|=3o!--Jyg}SY@YXb z?=7LSQYP=Yv2zV}D<~us-7`IJx8lpL`%&$&uF84qpZB=&2=E2f9cKT2t)h0?G-Edg z^}~sgnVqtCa=)%J+*fuq?Ot({(<@1Zd72X^-cfn{K;ozJbDv4(oy{8==5r+<4BvBi zo#BPv{|r)5mO*z@wNEZ9JRURO_g&`H72iUhReQW$7H+uWNYC-Wr}IzEIp?h=T~N!) zzVDFJv_&Be`y50TG|anT=U@589UY?5qbetMVuBTbwdAp-|C%Uwz@fS>E+o z3%e~Y`#n(dF`8pOt82=huwAct)^;`f+}Bh+XOki`!HwgId?j;Y*pp4mwDo8GiF7lwDQF*o_JKlL(OGUiu;McT&8Vd z5Bt_LPW~OVa>tyon^WFgeR=)m;U(7o3+I||$&uaiG`84x#g))Ib2Bxn9tNygdGurB z6BU*7B8}{RYjRVzOHG;l%+0PjIp$O4(c9V6jM&#k-3p&HPlh@3OQ%4HkO#x@oOze6 zW(Ae9`=nnMmD<bIkt{0>=6Ttr z&lfJKT5mBgU`kU_$^-v96YB3f>n=UKk?ThC<5TTiXZ-xGew*6R?pUzW)iWw&A^SJx zeMRYeUUcN7N^fS4Gd!lO4?mn&7 zRhBEX`{m+pesGjHQa5W>^kymc9C^q7pG%J^u>YsD~fi# zo!Y+tsf%-=RC8m*E6s}`S6ywhq}D7C@>j9s_HORc@?%e&(*NG-(ALWyJUcJhXrv{C z9Q!TL#j^43+sSEr;%?qH`)#}E1N*)&htBEOd}e_aSW zFJ#7XP-OLvTUKAL{b%q^-S$0e#jR5-ST<`g7$pb%PXC=H9#b;_f92wOw-xN>mO$Xye;BuWi+<1C*IoxN^6t*AZ4Zvzv1$g(LS36rwKnBXgeIybq)ad>o@?A9?wd2i zl&3~dsc~|Cfac@O?S|g|9?boUn)#O}Pgzpf(%&d-_)YM>m)_xT3uh<$sPwwj&dB@t zLFC}}V6~}=FLICe7kCD=`b>UfX>3^oLJQ1Mim(cqseE3#hO$fMx#fw`-U-YT_h zy|K-*=ko`qJr8R0Ydv!7Hb<1V2krW{Zth#X)~oxYw^liPxRA-);9k=gk zuV1s$Yi{AJqKQY=NOCFsWNB<};BRCOy7A#@_wR2(P5x#tY&uq)GER-(J1^z!kAy2} zUzcvXeItYGc$U_rJ9ZpiO^$Le1o$;4-jG!G|0mvg?@<^FgS+AB9~sU`0)1yc?6s_$ zxy1Hb->hY;r$0Pc-jV65=<2(|ajNhQ4khd7dmcyZ+iD;>O?b!4htGWSUhito+3~`kW#yr%A6Y&rvbell?31Ex zyuYG;a*^DhRpFNooLIm7UgNsmzn(g*cl+w;x+A*C*8C3ZQiUUOY>X!-&i~XuSMab9 zTi)fjZsxjY1eg42_5IJ_>ol>0&Am5q**$5^SM%MrmP;2oSxPdr$p5|ZpCMx6udMQi zFD6;Oy}CqZL*pqd3!saEeK_Gs@Blr=+Au5{C=U{(k;83 zMbETvlR2pVvWrVI#`k7&;(7VG{IR;`Rw-P^HShb{Fx}AU)06wC;OPJKOsBk5bo${I zk6Z7U7%xmRp1X0*rFCi?(*=^l?uqb*2W3V@MBS`0i!NZB{chnr2YDlrijL#A!?^u~ zdBPrD%G^}E+OLv7itUq=?CyO2=W`?c`tqN@o^o~F&yc1i6WXTn@8z7p*my_qxYVTc z%?_7ME^TI2pI1CTd6sco{06HihmU{yPtBQn?Uv}iM?0(XPR&tuVw^I;U#Q}sfv3eu ziDRqxIJcG+rQP%4nbXAa`nB(&!z%5b=cnt0*gw>L7h-bh(rI4}xvWPbOIF*q_Y_Uw zZ(h5j=Tnr(6pLegzvcOyWwVlNns>-cyxh53^J4vqxmPc#uXsDvv_IBtb+<<_XJpik z#wL+8$B7EZ7~f7_YGdAOx_4(~X=2|2O^c5<-@Yx8iGTRwiR2aYPxfm|FE5{)ddt>( z^VWFBqNS^zE%B0x3)E!e;(WsXl$ohCapGH*kU10YJ>AFo^?3;U!%5L63U@4?wan|n z^vu5@mnSSa8CARYcTL{|zxsV0n}v*ec$F4Sa#$`Z{-ZCOQ)?1~fS(Ypaq-a0pN`Q84gd5d*zKUpVCn)leiuXp?IchW^h;+Ox`ZROY$`o7fk ztLIfNuCSkZVfSx;4XSn7=b4q@!7b6S-oDW4-ks>Ie~+~Sy$rOosC&uL2~1j3OQrMglzozkHtoOm zc52h({9vp3L9>i(H(4&;zN3tRU&^}O-=sxG)Gi_UxtG)}mvhqGf1dmN>ng3fU2yCC z<@@HQd-feZG{fqtgVS^d!MAD-{~2C=o>Z(HeQ&1G!6}San(V9WE(p!NcJ&9pm+mXJ zSJ@?RpHJSpUDVb!+AA=3+XJQz9Ja@lZTrvH>UD4E?cCPx>hGs}eIG2TQ(X1@N}TrshJ^=?3-Bk{S6-ZKbo=mP$+XXZc3xlm?CSj) zbNaaw_lJb+_w}|{o}f@U$@o|QZO0`+2A7Tpe*CyBSJ3hN%fS55nF>0JDyl9T6OY>e zjrcA9<^9SVeme?g$n3nfid&b1a{U;q5qv;0D*W(fZMcxgr4(;K3f`B`Q*b%9CM?j&t9DUL+TuKcB`<@| zAKJe%_iKhjNwBEZQdP#f36EZR^c->jm+o}dM(+m8Mwu1u4F_(@ua#eYan-hE_da{G zcNhgSZ;@#J7kNCFF3-HJKOEPFz>uT$&E`lBVgcBbFcL%p+Drq2#aS?c*+)aAfO z*)#laS1#Vgb2NGOo|=u@qZph*RvjvyS1I3GlK*n{%eqy+7g{ zuO=mvwg9jW=c5vb*|j zW-iawUC5Ej$#;N3)ctFU#)|K@C!3dD6T2uXsQ3QQhoiyyd8PX+-iAA8#ZOtLdAgL( zYTgq!;TcLsDu4Ws&DnEqW2=s>{pIMThbFHmS{}=La(OhvE|EK@F0paBRCk{25&rmZ zm-i9f!2XPAm0$n1xE@wnoc3+etndk)fqaX9RW!4B$nNv2sd=p+alqio_n&`-d^g~=dEs1f|K0ASv!NARbAR`}T$$?Vy(BSr=~V@fKrXvW zOP2YR844+t#CEy7-fzEtuXglArdN4eq+|Ez``$A%dQ%;?=>2xRr@mLOu9$Xro1D?4 z*R~HPESdRs@;4)Wp2K2AhE6f@$08Ve|CQWyx)g9nMQ4Ji-Gm^aWZ6A41O;zfMQvPs zcEL)8icgELNql*(o-f7rO8v*CZEJs;Wo@{cG%F(YzF);TmG5tCo;6{CnWLrM&w7P zb|y{Uy?p-Mur0xBuht61Gj`u`TEOmN|xq&DQM3Lo$FD; zno(T&UUuhZ-B9z}M>CfuDzj!POq|1DRHu@a%W-a*t!w|~>q|2$PIE~vS@rtD@2#&3 z^WQCdm%UiDWbMAE3Yn7}bQY*O6op8J3vqUISez?;6gxBTtg|s+O0rzUl0SNJ_l=sj ze?GQ$x9-ZkHL4kns-z}zhiabg^zqhM?SR-M&ZsDb+885DEUC1-fb6$Bgh*w~26vESlYiORN58B*cuE3clY z`Zsm6nwyGsO77zrwSVgW8A7Jp8f zB3~606~8o1l~mL+oS^#HL;sp~+P@pmx8Jjqe7(1p^TU6JFWMb<3fdp}n0WSj+28)p zAbM=Iyw~J?uO(llU3>N3)to&x=l!j=7l|3Qvx@^DP-L>mg*REM^*%$dQ2T!qzE}GeXIjCjNDUsyMPaeqKb^I95+xNMB zIO1>*}^wr=;2sbEPKe>ahw5 zA8!%qILLC6WzMmEjE}!vd*-%n8!!aht+(00HX*UhZi`puM4#=N0hjfc zE*0t2l3_1kP)g#kJXd;t)`J+iOQ~sjk*lkt@9f~^`d;X@_RC$?b*q+b)%;_nD7QHE zoYzyC{`~Ud$A-t>%ZDBOFvTfpd+g;O;g4h2Y&!HLTct8xzE$UX&6>1b*LG!jl>{tQ zVC{T%!BAi-pE8@>ruhqq4&&0#7Eo_gsI=x3PKSuLbu%B7` zl@2E_f1x0kLk)pHjnceIvd(Iqz8Se{(aEL=*@H6Q7V2J{)7DtB-d{gT=+?E>^B(Vu zdSg=dyZ7FQuTc_9mo8ow{Z-UYPe?j*Nz-z%r4xjvv}NAHKb~KG5Eyw&tbM_ph<9rh2ZwHJz*Mw%G24 ziMlIyU3w(4;8D;LjRl<(=BcV%^_{x*?w?xR6^@yOyIvl*{djd{y!1oCdxu3%6)Kq> z`N}Tyr{3%HiU~!jyc3ms6kJwg1gW-<9ms z?55;SnmVI1c5D6iso`D6CSAHPb=!Iu8-IPDKIbPbjRpz+FWh@YzeOi9owl1$$&+G! zT0-6`llRcKCwB3&{)#W7Mb_-vr51Kod1{_2m&e0-UXNTjSQS?A@6Q#kO+IwQ>*yEt z4u+jyzbsDm`1)b@~uYI_}_Sx)R{b9-ni!dHxX6DC1u19p< zPV(99wc~bJfi0Vcu*_u7JGK|UZ!q-HvgQxfTKo1+x$6GpI~kgnU$5@=3SRHP;F-hG z$gF-o?-y5{`r)YTG}j9?j>?}_ofqxpnW>Wdz^|U`&yBn&^V-t|rvF5)c0J6EVqVJE za(U-TV@-C2#uLB&9A&@e{g~b^(X@x>hRyZEPiL(xc&GGn-%{45(sOprrpasb+j~V7 zcihf;8Ni*pGw`MbgOLCBw<#v<}Sij|eYbBDcf(&xedv zFSjRN4Keon>|3?e%Wh_?&Ua12$Hw#S%QRk%DfsauShF^3cc$e5ySOb+wqzV~?2ies zy6r06v+HO4L#L@nEc!DYPJC2(=6}oPad4S`zC@+0(>%|~`=@Ii34d=a8u#(zhi{Aa z&cEV&dc*4%>%MW%Soo-7*ZtkiE1SIRxT^o0tM+->yLZnA57j9zaVW5fA__L&GD-;bFbGiuG8{YyBjIM7{aM8yXTvhG0*%?2Z=QfD`mRN zH~$Lt?yj->Af#knCt7r~?q)^es#WfnVuJz#gxp1cJnj2zdH%qemvcUPbFmkg%xku- zk6QD0!s1tTrF^eMy_}|=>HK!8FPSNs`SDjPPQQsOwoSMfUHz!D>%qq3t(jI|t!938 zUaL5#!Q+&&{6b0l+U#y8>-GC5+`hN5xFq1!IW41YO_SDMRa-P~;uoEju6pVdv@DyI z)t?!DUZc*|`i?Pi)t*mn36U@2o^C2@jhgPaTXn0a%9^Gt0)>wyXWCa@yZlk3v~l^n zAHA zudw@xuD&#IlleNsiP5JO|N}<8uw&xg3_jiS#p>EYI<3;a0XhabNzm`_?ShC zze4V<#M3G-|7b_MPE%gn7q(>94KuB>=4n$yZ-?Gg+_hANS^byK0of(z=iSm0oyXzw z?aZ3?`!5Rpm7V^6xBhv>`P<#cxq($T>z3{eRMmXyAJ*i@X0eiany0$4;Nl05e9o?V z?d1A$U#(k~x{>y4F7sEGA)NPCEUvipE;}~ueq6|`2Cth60`&%OfA0BrdEugMtQ-tX zl|seaCrJN!u~2Jq7Vn(DTD9LYUvp_|T=mKPwCr?1(1!_03IE!k+go{??shx+rHS1} z&-nB4s59cb=LF2H-KeLrz=SEiX{V$zTLO=aVCV0O3#+UXZ&_#^o)-N{a`B}2PsLxB z{=J=T^7E*5W`4h(R-o2IqeV5(|6N=Zr?P`dG-ms+P4!-#Ptz>UDw&7-M}?bPoYwXi z*s^#Br%L0E%K5j?ZL_h7T9=Y^_R5pTPy41WIv3g#^=i$#f0Mhm$@!)Vx4LS-xx;9? zQ{wGUhaV?@UP=^O+3XNv_m}^A);(*7wYRj>R;B&?RkEV+c$Uu6+qFqz-f1FH0TVSF zjO7Jf4qoQgUU|kM@%1+2_vc@_E|;Ci87gTf^V09VPq}a3RL_-LY_=zCV^$0hGh$Fa zf8fBuD~*fFulb3uP?&#eotR9J#r2qp`JaDV$*S%>fEY&Q!p4Jz2{;~kSSuQH&XDbwUml)Gc&wx6q~ZZErRcK^95-;`EO z^?UOiIMn=;pVYiq$Is=qR%+X#D!EXZjT(wmp8tH_Rhzr3{g&pbkmtq+TKeZRBvtg? z&%7KVI!|~_OTOc;D>EhTtz2jb-N$BanX0RKKxC<=%L$)v zJ`<9+sZ{pwThJk}i&N!iSgZ7wAYMPSADlsV3QU-K*37GU@~Eb!Q}*#2mFgCwz7Nth zFE+frm6PRKk2b4%RZ z0C^8ASU z{+z7s{~7!<=A4l(zO=-c?fI!`;rnLXTD`YwmU-*ePY?8#Zt(W%>?uF_sIW(pE!oA! zG9hK5bnB$jl}B0b?8@B}HqqC6cb?g|YYtvBRz@*d9C6>$XK_LyIlyoKGkO2%KYoX> zY)rnnPcyvY#_ssM&`Yy-Z;$=EPcHmlTj#H>A=+0DdUdFGF*LtZW=h_YaZbAA+){_I zc&757bz4?dcsHz7^H?o;dF_&SH@19Qy~5Y`*V?OBuUoip*;U>0pJ68FlqE8CafeSB z&G7z~ede>oQS%K223^H{`$VpZoN8zg+4i47Q>I=)a^kNv6}K9fEQeW*4#r)H#Rap? zO7;DfgG;u3=eo_Spt@v&LzFIN8YQktSt zSSmBSNod8l4?awe3jbz_Dg+subn!Pncbz`vXtR5mwe}M8fNd8}Fr189YQ%mdg;Vyo zux;D2UXi>%yK+x0PF%_)yZOAVZ*lnR>93vIa{}&LsbBJ5b@a#97x(netQT=~VfMRq z@IS+xg{LQ;XIRg&B&+{p{;>%M1)I!d*Dt%Ve*J%j1@afncTcl3KbCYxmhn|pb;Wh2 z@1_mmSNB&ZdgVR1&aN`Sh55uMk9Wn2u^;DKJ&ZB#sa^D7lR=pCnchuhxAr}IY|phm z;g(>4V1M3>PqNxok9Ti-{C)20OKT5*Rli)<_cnX!i~Fy)mA%={zGn8Nwx^zcyZYmn zdIl`7<<9)i;CaqdQ)_}c=dbGz|0!I$#m>dt*mQ=?f7#cc(tkZ3e!lfFz0sssum16s zOa8C@Uxt0$_RD?yihr}Ma?AGC+UBxltuN<&)O*=?hgNo_f@h(xuAiab1+NgPOhs1L zEKPsO%d1QJ9x3(Ruw$OE>P-1E8I$yVOUezOyX|Hyi#@z~o7>ymDQ3HN$6B9Yp6C&o zsi|gU-8_HJndk9hHI4^z-{h-#s=KMoNw<{#dV6Wz;Xgu$UN=R@=>KAmsnwhs>~z)t z;r*hmUo4hQ3K7i@Zka!6DbM+O+uvtiwc1?UxG`Mo`0=cr2en1m>*t2w+I1{1L~Cu{ z(qGeJDs&gL6i#r|v6VC(on2n^XN9pRg-C zYi-&k9RGUH;V$P!&8X{|Oq>=>ERF)})!(nSzH{uw1|Bx+x4+-Nb+{9`Dz{;P?eslKk-5z?m;;Z}0a^K54 zwO`4s-w_^@?G(Sh*6Yc3naV8ff)z8=>e!EUKIAW&xFpPj@nfy~e}+#gVdptAqsoH& zzkHFK@igMt)t8OIhh|N^{VBqWGfL>Kv(ADhVY^wj2J;^A^zf>c?4Ra#P4v9qjPLBC z&!=vjdV>8-P~zOZDQoq*w3GFWE;$BGx*4LBJaOJJuVD2Kl|L_gHg7UIsjRf~`LuJk zM&&Ed3v}@|&5^gcXuCK&IA(oM1n=XVNNusx`HG>})n2}Ai1@RrurzUoPT<1`Tl=~JG8$1UA4ZXWL599Lm{*JKTfe~ zU9i$YlW+S8xqHeRe_Z(1RD5em$C)Cx@ciY9ubm?c)qA~pz1V%^&$U&xCokKkST<|# z_2m6QZ8C-Yx3yvmCo%ang|wZttLgjTBRRh@(p&b5-^JF%6NV=i=pEg1aptE3p>Z!{ z_m+ykzRF#4YDqzeS$Npgl@2D1Ej$iB^?@hPB&jd_lUQyjI3qs!{JZ{jT8#(Zd^G92 zm6`Um=U-{j{6$Oh+V)MK=p-E#G}U6`J0TY#KJ}f_`&wUY_EgS%#BWr@G^o)c32yX*Nq>sFCJ9!4rJ^+mQ!lQZ~S@L?UC@Yi^rZY z{Aoz2t30`$d+Vf_Ij!FoxV+u=_I2jbe~Mh(-YYc?<=%NR#<6_+cq7C4$ZpBs4&ut! z^8>GSOp1&=wD81-*YCspm;7f4ik_alw2S-fp;a%_qdPrv4^g?i(NfQlsl0m(S{!eVcIE z=(o5W9sT{?V<~stb9SK*HIn$PR*A2h9wWC_b8FD1M_FM)@lJbJc?PK4E)xCw=f~yc zGkY5E*-wA^`+`yH#4o$9F5GH1JC@Z}ptw0_solGvMH38fJm`?!|8i;h9T%g7x3Art z^Ax9~q-Lp?y-U5J6UuM2EM{f6*oCR{6ob@6itW{mJbzemNiVDTttK7t-B@_Xs>Fiw z6~AxoJQWpv_S~kGhgSZ}nsC_jaUX|^e_l}X_DRXEH>34$-gt4Y^6kSvFTEA_?=X7d zcB3kgm*3^roZtLfi|el5_ByC4H!nyUnpb%6kWA1;b+he`T z)Xu{GtQ$*j%uT%MXj&%yblZOh4-v~CyNfxt+$TJ^8Wz8Md*hK`d!Dt*#4_V!5`iMY+Mq=2!5cqk+NY3-cB|%r81!;`rdHbwjIS{1wjw%CblPE_S^CAyV}4vT5oE zGEJVGQt`F4{JP-m`90t2?(YhY$~Twt;@a-y?RrpV@~5Rf>azPvp0|HEy?W+MCB`kE z726p8{9Pp=sza1ws_q=_;}yH zD^X=#M|O$`R^GUh+RnYDS7jnw;gRY}o+QUPSs@QQI{)tZHqX*#b;gg?tqOC06O_o*Jvg1YY zwxbMFSXQRS{%2UFzO$*o&+f+c^~p&t9k=@`ERGubsdR7J_Ep>YvbL!BvSlwHFxY-@ zC_l4If#a>52YaA%(T4J44t6Q}*P1e{W4;|%>(wY*D=M<>tahHJ!Zz&|_d|&(aSwO4fvC4{z#v*ICgfQq%nI!(|{LTm?J3hu^QxE5?;p}x|?^Kyz zxMA&rHiyf#~;m=w;z2_V!yRzTfhbRh}a{$x@?5Sf%4FTTOOGMfHht z2FA;O_8w)M6KBSsZyXdj&wz9X*iKpCmsD6JX9mt@u(!EK| zQ-E^S{$D!Z*#>^GNx(g@y^$NW@dB$MF*ANdzN}xF-`ga`{TaQh6E4h zM}9r~ri%2vdp_gO>+tN>pFwpG*PzlvV#K3kQ0+tWI$*V}#NkBJ^3Tg+8hS5Mk~LXG8{ z?3ZKbo_-ZOAo;S%|38C1%ActO#quRTguf{&! zl6^}|I(hXTyHC-}x362cYRSF_JuD4kjW^`f4<7H$&CWZ&ZO+6~n+vNxHYm!zvcDDn zZr}Zk8})^`Ya6%A1O=beTv_>P^2`|wYDLKk@|IVOmX+`7SVR=fBWxQ%p}(+FbE!d6KS>k-u$A!3(L9`7(lE?(JoqJ7dji*V)O5 zZ{IHV`MEV{)9KZZxL0Wf_pSI5)br`oRHI~tA68Naj^>?N8XoxV_y- zrrnwHOTjx!Q;=mz<8j5{2WRS*%vW}}zd~<`Kt%d7ajX=d9`i|H}(!ZMB%L|K(@r9Visc^%CxHaEz9 z@-s%|dkc2Voavq#^Xk{3U)$GgdRsm1+>58l)4o`|%6Rqez=VBTQ>I+D;Aj={|6C}n z;3IOqPXF=!;(S{p3IA^;12i^_G!O3+Ki(THc>wdHb{IuaAcnnm35O{?4v=SHx?vF-SHV)o)v4_(x}>vB@T_zC_Ox-|XzR*nV+m*>|Ai*nbAA zpm(R%F5e}RBkTRr-LGn5-r|Jd;Lz~fQ_kjUYue2A($>6k*GYL+hs~AL*aeL1CV#L$ zE<34A$!FC(AIA892hwkwep-G!{(>Fz(RH(4IVG1_s#ZQf&Yp8=-{i%zqFe9X+g)v&t#o_7R}%y|A8e0l@Mz_n?au#irR&;-(-d2u@3V_D zJzTN>KSOu1t3sZ%d4~J1-D~QW6IXZyg4p^ z%WqWigg=pC%$?w}_O8sX$M?E*%Xf#qoAuh+)8}ficc$yc6K2Z}`Z-OVDd5rfdi81Kv ze}+q(J^~y^{AQl$RB5`CIeXLlVr7{wrbTuY|^?eoy&e9_YuZN4s_oMxQgm-%9wYuq*CCG!PmvDH1^ z~Gc7=d%X{rYg$XGFQyC4vGw_Jj@ke=5+qEV5 z#2o)wJb!=sWoUexzKYS#9fzhrS+l#pFpN?B)$+LI>y|A`Jr;bzs(tF2os%aiF|zQS zX`Dafd5q`&mkU!HCdn%{+?Tao_2ydBb)E$YpAS5Jxwr1xo#4WC1^a_ucY8CQ^YC~O zwA3N|KSPd$@C0SYNZTI4ndv$8GH~gZ0d2aHX)F~X_zCM5Jt8E^zR(jsLRaawt z_q~$Q2-$tYqM@Lok(1eg#piroAnSLo%0Sr}?7>ZU4$95dl3df0z4T`1x0S!Q-CtC? zv2gJ!4{aGwubt|Tl{Zz-FHkn@nVTk^n^Sti>=j$#%iy(#cqIAk+Mjzz-S*C1vTVUd zj*QkFQ%{EQ2-F>z_xxSqlJ$o!KDV7H|GVgCU~$ahiOa)mwy)T&`|b7iX{+{bFJF`T zElfn#vzftPAc>`ri9NCJR%-5z?~C}n`I-0ceCh7ZQ|=h7(zoDE*4>iruP$v|9=YJ2 zt7Z<1iI(Fxl|;k0|5lz)NGraj8zy|yXGPkK5dC`0v1_n@-a9*A&NDEb8#VCUWM0z4&iJ{|Q_;yWr_6_WpM=Ee7?W@%N=z4q8Qcd6=OpF>@OVV3Dvj_;^*WavCs z^J3{Vk0lqncGXnbT7J^#HHtA&a8S0-m~ynO%gC8c=}4&7%wz;x4dQqy9U zzLWN;b-z>}N8E|gEBlbU{dVmt>BU;749q@$_?xZwi*eMY zr^jAJ*q&!{(KzDy#QogXkZ{|lyz_;B2Xe^V(TmP9yj$kgy>;TTl~YcvbgEpSYcN&Q z$X&ydsrr@oj5t!$O(`=P7~4b6We z)VB!UIk$LsJ8X8mjX=E+=EFj~Z^739$QgZ=G)hR2ax9tPaj%oO#OTWno*ZU3DK zZ*Frj-xu%NyFO`0+={oR;#$ilO=spPRrF>S+9YhCQoo|-+=9&y4^?bRR+#@OILf0vsL-kZ)|4KQs3F&UiV3vrQ(wM!*%&$1wWV% zJpOcV#u|E>Z7SzDwTbjXa2Q^OCQvk zC@^WR`pW{~#9C-itd~S%ol2x_cg$Y*k z>h-o}W}b6yK7DG#?(G55flnH?R_|z;!N$n4{ITr5e-7%-cMkA)ZA|zluQg9|hS_$R zS4toM$!}l#{?58k-OE7+H!H7;>}+Hy|5V3+=f#d6#hw=OB@@q>xA$Giy>hW5#*m9Y zc;l)!eQVy`UhABn+q>SYU8X>7?yTjOJA+#5g4mcGCn`E6Z|mRs@Y*9|mGjf=A4$CL z7w$i`%J*2vhBq4<&#$X}c`ka0u*&!O!o3=Em5WzB zEbjlr%>Mj$h>^ANt7G@0de_~!Eh+N$O8j2wNH(sZ%`aElCCIG)Yq(-f4$Iw->NWKf z?|)gKm#aL{Y3hH5x92~twf3F9_>I`Bm23E}USZYbTIp+VG-cvcO|5qZYx?(<7Cx*o zRr~k&?BDY-=`SBf+4F4=Na|@-pC6U=<~84s8&g;PXK2cwF8XG>=Ze&pmOYvm5!kn{uI0Y{$Qw5=Sk}V`u+Ux-D)U@g+=dS+Hi4 zlSh!3=dqN7f_=ZfY+$*~9TZpaJfix?@xH8?xzc=3_82@}b$M%+#_Onpi}nG(y>dAV zHy@a^V=70}jQmd zuI!QKWId`eX^DRW$NSqI%8$L?r%m3X(Y(ve)^_C=K`XT%=a*@`dobax^p&?}%>hev zHPml0ElQ2-`SZ7Jn)0Gkt8S{)?5$bxaZA;Cd#)u{)-5}EZSVTxJEvo9wPqJhT|PID z)z)i@!!w^k6+g$B>TgU7r!F#-xcc*VZPsd;b$X8KlB+MeoC=ZFS<31DWu?cr<%SH5 zH|jTD4Sh1vXu-s1{tv_0cg^+SC|`L@Gvu}_!)Kd=jF+m9&w0M%uNvF-JzJ+vFxsF{ zYI9cl+jq{tmv^pA)6EEHWtwN>Rl#8Go~*2X!Y(Dt*TAp!j>dV#(-OCuCW=n>o^$2G zvgIjNxqEDbDmv8f75SX;E40tIVw@}JlG2}lZnN@{=ex4XZl@IO{B~u*mQNnxf-DnH zzf(AWKG#Df=wQTyim+8D__e0*kK0!6qr2{2x$3=Hy&NrV-t*5E9AGGvpD|N;!#g9- zi)MHG_L@E4`}tRH+>T9=(j33s+qCD(M*nKLGdFnI>ctCcZ}XjZgHs9_6R)Uc;<*xHKRb? zgT;?z6lJDNycvFYuHC~qS?}BLmR&o)v_0_rbFbc?7guxd&M)q^HUDE?7;4?O;kW+U zO}BRNsd7%>uwi`6=4hcFdQbAN@k8ArpNY@IJD>jw{iOf;{E=@D40fu!2k#AzoAP$o z-7`=BUEHxgZ*S=mhpL$|6`GE&PDzZGoCX|kze&x1^q=9SP1!6TE~n&z`*x%$S^{;ozTM6FZw#{tb!&tUQTeMbc$1nE}^|7pKmhtpStKGYC=G%3l z<_CQ3f{rv;t$X~;zUu8Ab5eJ^%Lf*=Z|3T&XLw^wLBBprX23yU^=D!jH!=eGGqdNwfIh!a09kT^3E7*7r2f z@6g+j{JH?<1OX!^kB~`zehwCIB}INDcUfOrshqq}I{A9;tDEWNnQMD=MC5AvPuH$W zx^`;qm!?T=&(?ct8NcH5;`*W9m(2X_%K7|E#jCgL|ET;evgb>DJ%9DMrL z`ER$wrK4NswM_16-W~gT()M}I_3bQ6r))dj>gCWl)#F_9_MiW5^~ot0T(?@zQaO3! zSDE&$M!TBC940>LIxy?iuU%S4yQa-r65+|K>Eg6dVWokQqy75&nHTo>w@Ys|I`XWt4UkhMT!y_@8ORHz z^RM*dwHebTPMuhlYR!}TFa7&eGIy_a^9Ngm}?>)k?N|c z364tjPH85&y?c*{JFY8S(C1t@-*K$Nz3!aRp=g7W84`=_*ak!mOFLSx-siWIt z{+&5lEw?W*o%+vUA~ZuqD0zyH&)+taN#8UTb;J~Q$UnXJ{N?32VB-ADO$h0r6ulb_Do zUG2T*c6mvV!LqE3td$`%pEOi{QdXG6mSiZzVZUsh!QW4BX7n$V`Fl3_*~W?uGE+Yv zT&8q%(Ozw{SJS+giAb?4x^VjRJ*i**cF9xaW6NXGSKN7g+hWam<^>GAagR2J1WZxQ zW^CwZbPD1#{`a5Z&Wp}mljBn)&+$ExKVBte$mKv&2nd%P}!4kB|OaaiKwx6 zj!4Ogg!-+Z~bcQUgst*_qlvd~~bT1xXf_WgM`B$b^$yk5EXsi4Qq-d(-!m$O)& z8Js`SmG^eurl?h!hpW%ce7nAcp*=LD={8Fz^X2%7Y<3^-e7m~$&7=DFJLUFow^BOw za;8CwsDAua+u458UatMBUT}Sf(3Ifl3o%ibJeluIWVT(tzf)?}haK1BBu~iAu6Z%B z(jxHYHqFreX3K+qwQpUYzwfEuy~WOUdu_LJ>REkJOFWvuXz_r}W=^kl_Oos09`-x$ zb6;k*bWfD_?gbGO?k&!0dKr4}a#rnDdtZN#mTl8k32pVB@`~d-N1scA+qUhI2i}?| zU-Q20q~k->odWf5~?PzbR)^jdqq<=sv1% ze(t}pI3P z^Ixm}+Vs}3Z1G}q>u67#8D5Q=F%!>fh7^6iZ~52cdt>Z@Z@-pU)fWCSQ5Wib=6kNz zY+>>)y=kXziQIW3I&F9IYYjfDmfus>om5q0IbdJE@nX1)(Xrs$(H^GjC*FVRbxW2Z zx54W2@nz9xBFs(g?k!(axwsPwWy`nYg}3FuT`6`uweSO9t(K>3`K1j0@-OYV`LD`@3zmJk z@~D|lcjM`aA$2K&ERD}uYF_M){H?+%^__iQ-IRI%E~>^lJbYce{IA~rH|s)@YhvGT zGpP>t4>}dLq`~v>{TZDH*bPMI9eL)Q_xC~j=U;E5ZbjQwuibw6>vC_C4zGYEOLiXd z3Rd|p$3O4>vWZ7E-7c<<{JMDE0wad=8-HGzs=IB|f^AZcsdCpYB%C^^W?<*>_TS$v zSx4TO=G3mv)RQtWxGiP8@K&y&e_(WR#PTJMTHz`mHCgx$OqNi1e&pXhF@_0GitMH? z+mqV%ctJ#^ksv$SbB+?7wE3L!>rmM}G>*#6_@eg+(!H)&zmhwX8SjE|;HQSS&!SMz_` zdHw{`ZKrF*Uj}O~7k~Nd0D}XAWqo>V>dBuK*DcrdguHpTNy@x6o3m`~ zE%ojW-F{!MCtQs}>?(af1e8p#RrCn?9y)XC;1XqaYr+17Oa5573q6^{_~Dt&#)yB5 zbHDTiUbto(FeSw7L(nA;%>ZMW-!pueKdty_`E)<;fu-|q#jKk9iQ)M9xiLHDtTYMS zGJDt0i9bT*4&4o73(c#|{b#o3_q70iv5OsA$}BV4zx~eQGd-}7OGRsq^lACOdum_Y z*r#3@%GJbG)qkkFob^%He}=QWW4A89Q@E5_mvttCV^Dv|1pf)flciRF+~2nLp|ec@ z)9w1{52J6?B$hHJP1k8ZAhy=aw=s{WInZhSkrkhcZ99eKx3Rxnb>%+;`z?=K_jJ$C zjm_D=HeSS4qF3+PfiHiv=bMU@oL5`;EA7>_1=3jqdP`8ymJUHUA-{mWeKgzjwu9ctZ>J%ifse0n=EysIa@@(^) zEK=P1x`)YM&mlir_`T2FMU~z3 zwjW>N^Zik6tjSyadrEFAUpHlYYpu1@?9{baER&oMi3Jp%VMulIDepAA@8T>IwAfSV zL0|ge+jDc3&K+lc75U`K71c>y%j29XK6^!KYT2IfnZaxk@L{gY(I?w%x6ECV{AbGq z_ig1XoV?l-m!I2q>94fOs>OvK%d#hyY6QfnT6{bCyWirXe|@Uxf`HwV=N|Xhy7%m2 zRL<@x`L+MHHs%HCwPvS&Ulsj`(>rI%?@84&XPjeN#_}p!PLiW{Nm*#v{mX~HFBRut zo_cvnzd-SiuPc^m%vzOO(#w7+I`ZH%lC=zx;Wn-a09d zLQTuuy&9RbgJhTaYM=k7w?Q)Kbc~mRi}r6OLj|7)yrPxNn~rTh(-&Ntxbw?_CCqIz z=Ci!1pYtnd+5McQ3+{+dV)YL7wz>1HA*#A7L5sca^6hQLz17_{VXE80B1@*+SZ6pt z%jgiBcUwjE@*`gtEzi80=&`O)<`tuMsiT5s@f{Tp2AKzSv)08##5fzwd)dKWVE5wt z!bzD=Yo71DKG%EEm#(to)>ExH8MzEL1^FC^4>LjKmYQlC7Y}W4gN;$T0J=f!3u`qGdtVee5je~rw3s)4j z_`2GQPSwBkgkPav(ZBKUAOFg0(?3t|u6g@lx%}VFQMZpT%R86cUv;zUMy!|YzoZG1 z#9nn(tkqPue7EkNz2>!RKGSt?-ag(Yr|_ia4>p}CR&F|e;`xndy|dfzcpQFXq5Rs1 z+v;21(`74HZn?YGbakYsNAAhM4h04uWO1GS z|MPPDwV9!ByJpvB_Xk~GTFO^Fn_1C7;ZxsZ<$XPEx2|21O|{=q`Jdsq7nAi_qbW&7 zRef96dWV7_T;z5MFw)OCWni)j+S-u?d!TbRFJxNi1$cC+03&wmWPO{V55&-L5x za{HiPz}4VgtzN;W*i{98PU2%P{JHp?OU9B&nX(?kI0wP~+X|lszS|lk$D(@R(gn{) zoGhHj1s{BWV%>3WukJ)4elE>-9_PYY!a9TI%n#H^^-@?Sl%nRw!ZG=%3gm&%eA>ScP@^mb|Io zKCABPk5zSbDJXO}@=Ymyf4=bAuq&<_iPQ7Fv^TmP+4(eZt4RK~o23~QCnlcgQ&?)d zvBQ2(`9-69@;es$x@%1N&+u5%>(E=NrjRAuUN-S>mDw@hcKPx7+mbx>LcBTJKsf-Y-BTJv|cMH1DG%tX~@ucj*AGTj)R~_wG^KM)g%?Nm8enL*tKIW42 z(SsJhWMQIgRf*Ef5yqKWF;d+qxoIVcz$J=TA@g z8GPGf(J`szy_z0@UKWL(8ZwnjIRCO7ubk^N?VWI3^<5*!+qXqBJ3TKMH$U{6d-GE6 z&K>jXwsNoC7Cz7t{XF6Ytg3KB~Jdy?SSd*u#R#=kvER-}F>zIk|B; zcL~?h#4X#xZfAM~F?b5F@Y%B;;s5$(vEt--n*`e_~I%9e8QZL;@ zONto0etvIYV}Jco%4F~H#0Jq?^Gh{`0mt(WmajiGOG;~(`-(i1lBo>y&n@w{WZ(Xd zulhejEpN8U#M=g()35HcdBqXhGb^UtH~Z$cZFe@^sXwBz=F1tYn~Ypq^E5e`RGw;m zmOUx^*oyPrJC(Hbm`wX}3*Gs`Po2(ZZ9Ls#UAlbkyl2rdQ#ZCQN>^AhqhaN5{!69_ z)Bb$(iY>4)p7r&}Q@`M<;v0IaJaSuieY(xU6p|qN%%`UQ3cv7r&jN{F(n$++@& zZQH7q2V_-Q4>1N!*H%%fcCa&Gk-PD?bnTMDMRVhOs~#$PGVMH;v8_8Z_)@#pI)A09 z^W@&82)IwOySvQe!bV4viLLc}>~D8ni{2T?l9KNg`}3>n<+yoji@NSD&C|Xap>Vlj ziP1x0u9$=0z4mT$`0Lj>%;6MnHP z^cAQ~;1ujT@OU5Rk(pm&JcDEndIZlOuaCMG9nS9gq*miq+uguIr?(;efh*SYiqGd@ z-t(M^rCFZgPlo&!jmDtDdv^aB0#7vvip}=RyjuLyD5H$|cVDB4nYNeimh)z(vg6kj zJ&lmn(UbLd)DfL{QlnrgqrnuN85gxV+js-N@7c=SpTPL*;@-~WNnax}R=tkjAG0@Y z+Jsg%r;|Po2FZ4p)%VOiBD_#}+GEF4O*LMRwoSU}^(5at?ANimi+37^`)Xxq#!qf} zW|^#3=fhL`C3U@I@02ASr^0Vk2VKq!`TjH1;#b>^@7A%e&hB5a+WXq7CmaQZ6CR2G z3^52+->786R4g0(@bry&cMiQ-9B|I_yuH@ixWh^(RD+!Qxvt&6W|jMP`>a*V)`_er zZTVFC%ITq3kIR%McVQ#r`z){LMQeTd-9GbRfuHs6zuF56Ui)u%P~5^$u{kL2^rg1k z_w5qv_V1eVbivuF=d^7YRK2y>pQ%*z8!66zB<=mh?4{h}zqN;IIZxPY$<;Tcw>f<` zYPkP9`Q@)?y*k&l%(UkRi@Y=2vU2m0RSrP~D$ERw>U;iFT;lFd^XOUUZs+oT`&9LL zXWZ2*Qhr<(U6p!y!-@P4r*b{DJP%A%DN~o-U0-u<`}~g66PNHL|26r?J@3K$>l%~J z2IuS#47n=pH+9;r%$Sx5lO}mNvN5Q5C{^?QShS)i`q|5nA9_FK|1?;vo;bJV?;Q8Q zSB1{+qjr9Xl3BE5LG(ff-90LNA5SH?OkyiyKel!AzS5ovY{t*(meh%))PyQcmc0Cg z{c-$?+kWB_(WNJxL?#OyIvDX@B~A3kM23m?3|G`Cdg{rz%HPY27XH!baI(bbtV%?_ zJmbrs*Y+Md^wmn>mbUmA4PD1vjkR1_e-5AT`6*y5Wny}tbK9GxNBSxgg!^L}`*SWB z-}9Wj^rZTKhKFl6yZp?*vSL-~t;Mut;lGu+B;?j{rqoO;7cS z!gFQ{J}c!t&fa&=b%*r!NzXqX%{`(o;<4$pn)~MkXZ5d5kG`|z%RwH^*6O{b3%xZP zZ*MfdrBY#)bI0vo#qYQ6A5%{C`=pB~aa_wg9eMop%V^P%n0YHJbEK9wLPI~J?XP*D@etTPG*oBWe%QM&WRi+l1eO&Q-u4xvxSFFSm$EXQ0 zDnec@OBjp|4Ql*zH6C62cvdRJ zr>LpQ!py?__&r0DolS4#jyaQbxa5o!mG(JDcz!AzQ}cKv;C3X2dHs<&8gjE|^_?rW`}Qtmy-nAxmCM)`ZoBR6 zn&EHs>5RZSMTdrZvt;&zz0z6VUQ13CjGXsI(w;ScEvQ~o{G;4s*|ooE?t#c8uDI2 zWuXVFl7nWSvPx&*F}6ZuHq$wQp1e`d*cw7?f+b8FI zZOffo@3y?mZ11|^bs%bQe${8??H%XkCEl*&+}pU@+`{pJ-Pg#={gNWuGRe!IyM431 z_U85y>C|(3KOSIN8298rGpFQ-IgN7mvF2NLu_Y=^ivDYHrpm@MOSj6#CCw_UTln_( zR;yLgt1{MR{b!hSxw&g6r-W@M!}rcPDX%gvs!JtU*r^0fX}ub`xl1c;(eH>?_UwsOa{~4yspSI`hpL~PCRQo`a-kDP&ecjLZ=DC%oTNfR5 zv&~vid0b0AY1-1Q3@?HjR9%kDVW|1H@$Z&`&7CnjnOS&`8^3npuDBqJ-k%I6E zKL)#WE349oyQRf>wOP(3y=t9oS37fq*B-mew?$OdMc4AgV`JOnXX^JY-dL)aQhN32 zX}PO5-8W@)Ys4-*KD9hGqm+|nE$gNbr|Kzxc%C1tyVWI}_vYnt<>Q6@c_F+<5AQFW z%~1LN)A~M<39C{LYXp4fcruwyVN!vM{}I9O7sGn9IXPB8Ztj;lm06->W?g*Z_^Exe zi3_-{T-FZqTB>HLuD~H=Q*-|9g8Ut|3*PO&FWuZ1YPxKT)5RRtnx)HZU9As3*;T=m z8sz@7Em@HH%VoVo3=6*eX9z2Bbv4;>_xt*mZ4u5(y%J|t`0xJQ!NhR9@y4HTA6KYs zFm#iR=6-zP%5~vy9Zc6wG3d8VSuRpBRbc4~nMs`X>at&jJ=U$gxjlWJ%lrPH&z+~2 zDi%h+kt%6j-dSqHFj09T^X(5Rm4E$j2j21&34B>;YWVDig89l%Yi!PNs&%}OW!2M7 z)wZel#311Dx$hqTji2wAotZTuv*xEzT?^Ndv!`uVPEFsvV2!F#xc!tRlNiHi{<$b< z@o`1kv>V?WuetloKf`rVNwwP4VtLz@eW4+YFP1(jj{NS~B0QOWr_1?+!u@kSu6y20 zNPjx(?3R{tcdhyxnc1%+6c~OnYO$)ERI_frRsG|@HPQamHN`U9j$hicYeEy#<*e@9 z^|!X)&DBt;-YLW9RMG!d;Y`_2E72c4fg6|;Z0p-Ls%P9;o%ec|RsE(JpgYCw>)o(664lY^sdavEaZ)ZDpdv02*7pvtx zqx44ZqD34kLY#I_Ch;WM=lXxExbe+(x_$ifW!Y*W=eA28pBj?;@Vf5XE9$bow+j}{ zYd9_?rSdDoXUV^OEv8~Ei}$xff6w+5b&i(ReEitWu~Jg@;^*gHR>vhGze+gOrkWK@ zX@0f*iLg=n-)i2h?_Pb@yRV&ANS^yb>~m3;nDiHyCa;isjsFab_We_i+x@8QNYi7M z(sNpzGOsS}&G0;Zdy43ptC~lR8XmRm?hguXnN)wLe~)9Yt4ybzsLGXn$@Rs-n+=&y z&9m0~^Z1-;!PY(Ay&D*U{GYJ1-@W0M($oBL@$=o=dmB5;UTJjX9yeR|gwN-cP)xa%T!G5);z4gq(CJCS8S)#ElWC5Sa5szg)?%&g z_2wxtiA-f$*{iX+E%U+|MytxcnEwn?Mv;#clZB?KuMOc_T3Ysg<5c%;Yx$*?uPght z+xSb}B$=#zZA+^BOg@QC^|EAG5Tv51+*f+Bj^{^0A*W-BR>PB5Pq+OJ(o_AY(NN}B zRGDyT^Rigi#9b*luEE!Cn#W4jZmnBs-=95ytA&*U9&ho_lQBzPTDZ!>;Xk|DWOMvxR3}Dya2%DFs#-uvog-mwJ7s}r4kqO&w>OEnWcqccjm^geR4_c$GY;UF*koB7C& z!(Im@8s)72O|f0%@$TUFc(%o_Gd6aGw5M)&)C>x5nq=zVm-o0)pzK9B!| zPv8Gz*8a6;+KZ~UuK4?`_qtRr;zZ=MvboxseO5Ohy1F*SBW z`P1zWqqOgYeG8Lf+Iihy!IHstk(!77_Br*r5eW~4Pl-k)ot}Kp=7YUF@5{AEzc?m$ zhO-}Au-ZK7+UYsDIYI$K(NUd@DsIIeRv!HL*z5jd?~v(l7S2ds|28lteoN{ymZp7d z?Q(aI?%FPtF?(sIccIEQ&#$3JI#g!L+1>nG5>md+oN=jJuF8{s;rG$*1shk)i~FFS zFZ1j38jd|H4@uiC?l)=T@N#fenO|tcdEofW_Y2=w^zLjnFg~(pn{wfNu6eHSg-x;w zjOXxN>1FzO-F#v=f$!#f=FpCcBbS8w3)|%1s()Ov z;a+r<@7fu2e)3qB`{r0_yMQ-P%!5r{}uilBwYJ z>6fQ+9^0qjp~|MXvOkE`^tz{$^zzDSA-krs7V6D&(DQivn`h=R)8(SAjXK84+aA{{ z)NJ%se#Co2$W8j&+7hE>^U^OS@`+7j?g+DJn&2)of8Hf_nQA}N=q-D9O>}As^>aLV zwcq(%$Lwx{0Fkh$Q-Us;thPoWmrfn_2%ZppTcz<9gR;tVtNC3H&otgHn(%^MVP5-3 z%eB3qw#l|VKAimduXftS;$^-Gi;JeM)NK%X-K`n<@Lj9Re}+J=pUOfe+_C42HJaQM zcsKbi{Io2vP~*nmIqS=RUZ3l48gzN7$+jipFPCO=7p7=5sid)Q@t5dle;#$?T#(z= z>HAI;uAX*p>Y8mCKecpY(RPV@C z*FMAMIn6tsKPdLP+#w#YdgB`XO@~4Rr`jw?sY_aJ{V!R_!&v>~mB_wVmWmxOG*2^& z)Fduq>|FZn;dZk~jzS-6pHmepEN&c-WMMG=JlTHbrRbOIuAPlM=awxLn;m!4jp3P1 z`GRc|58m2cFRFNCLeNavX%)?e>}(v3cjiW}{V`dh$)i~=y4LOMl{d>%JB;RJeP3x7 z?W>z>8zt>8#xEk$G*7_mfPzL5`}|vWznVL=JPsZ&bKZ9Q`JIc|mA8()T&{fP`N!4e z={d10GVW$hi~Abs!>8c2qep0^ZKIugFLP;DSg{0y0rLW7yRXOf@^3y(IFOQiBIMGG zc$0HRAH3i0xH9SGOqt*l$zSL1dC{GHJb3>-=Gs<%(fv2Ccv#B({AW5hRj-1CewX@BUAQmqslR5U zvG9$5yRr|x^-uq{SyS*UvsM4jmC+q0Pdw!f-$r*7?bWthYOCE5(z4LQXQiX7t4hZ~ z%l{0YmmK@WxH9e3zJeW3-*=_-q*xp{zhF^!>AKH>r!Vx~*8lQ-QT8uv6vVqVywK zC-f^CT6s<}F<8oezcqF1!-*TTPTbbr>$Nn|Z`UfeYtnpER$f@DDB#C&%+C1sxz*Ec z{Jf*v`odJceE3UY*sZ+S*o=8ac${n{g?<{-#N@wAfQwbl{_dJ;>IAh)g(>>cK zuJO0lJA3-n!mCS`eXX0zgx+2VyOnuMrDg4vUz);m)Cw&Aw%OSo z_Fi-5l26Gt?Ub9FEW?EEs|kd?(9GGv#ib&A+;IMkAAi4^PWe=p<*&ET-mbJWpW(jf zr&-|^s&?y5K6>>V+PGK#p8w}uXPu(i1jiedTwff8NgwDoBep@j|12C7{+F>h}R#jnm?+rSo-xms+)?LLLJCo_~M zo_xbK$<6WP@niX_eK#zx6<7vb^i$@Scp3cmZ##?|ShFLs* zq~u{CX)(!Te@1Vo^1O`K(YguKY*x4z)Ovn3F1oZUuqt-hJQZWNTY-P}DRBl)ev%;A z`0I>kx#|(uE3D4%=bs6i;BGDMFLP|_ z5(JDV-&SYwFkJbrqNdz2)`7j|?X`xtOcT!UTxff2iRM?;Y14KkZS`a1_HS6JaI#-O zC0p>r=ez^LdnG0}o}QbKH0i$Y%U!dJzsf9h-1MJeqeGeHiKg@?6YqPTebQa>rm`<2 z`P-=?|FY#8_x48J&6{g#%ILE+=vFA_&zW3u{`dEFZhY*hs!;pb>A*A3O?TRLTRm@o z-}^l$=4HOw>8sbS7CBdx^?sL%P~W4Wl^ENh(xf85c*Q&>_ra9TlUDoQnH@jX(Y-Z| z*}5VjWZK(bZ(f~u`4xF()~j=yzp@>@ylZv&L;aAmYyH$VE}F8C_k+Lc(irDlJ}wW9 zDVmNfue{}qE7rNV2fpGwu=w#C^&Q*`*y47qs|_tpuKvn;&1!MQtu?u;o&7ClSH-ST zdvbMuZZ);ZGo4#B2(!P)30a-F@>z>6_En2LaCscgmqn65?$t&{ySxqYwJX&yO z?OBi8x0Um=+HN--6nPes#Q9;FZP>%5c1=^=`F4pc@#^sOI5Oj}Lxq3fh?sdLDiw%1uwIZaDimLF8J|NO|LKkdEM`?tZnPA%u~v(5g0lS`UQATu{H zJykzBKPSJaxF9h(RfwI7m5VvZ-HA(+iw`21mYI{9mzbL>B+ezuC7fqqZeea{VQ3I< zZftI%=M~`U&LzVo22q-yl$DxXqMwwMU!h-_o0%uXB*cJSwjwvN0xT=a#SPJ0o|#gT zA;c!d!o~E3nM;{V2qK)ElUQ7=UtF12oLVBp%f-#b33mZ5lcDxXaPdHlNXswEO)L@O z;9}!q0lQb4OAsOhF+3+RDK!UT^I>)_SuSyiB(i59N|>Y=xWr+>lAMv4mzSDTEW|3s z%*Dw3#`*eBwR9#15XcDnC&{(w94D6;%n2E(nduoN5Iepzae2c79_+i4)QS=zb1qXZ z>M8uwOPa~Ru)URmLE;ud zOl1KB1Ahwx!?yEC{t*MEmyD8<0xNy}^73-M%+zE(P=LZMBB%ha5;p_MK0T0q@UT;G zNli;E%_&g;rD{+?SX!h2E(#TN(GxM*8j+lZ4Dp_^d> z!&HV@4D%V5Fsx))$FPZEJHsA^gAB(QPBUC!xXN&w;Q_-_hSv-q7``$5W@Kb!XXIrR zW|U-W;9~7V6ikBnL?OinbMgGnW~stnEIGzFfC$Q z!?ca*Ak%54t4t4=UNe1VW?<%K7GqXq)@8O}c4qcxj$%$@E@G}@?qZ(Cyoh-n^DgG& z%$J!TFu!5`$->4W%%Z@e%VN#q!4k@n%u>iw$I`&<%F8Ol zs?BP}>d6|xn$B9z+QvGKbs6hc)}yRfS)a0gV`F0zV^e1{XY*i-WXoc!X6s>_%eJ2F z0NX{j$82BO+1Vx8wb^ai{n-=QOW51kXR@zh-_L%D{VDqo4sH&44kHdXjwp^ijwX(2 z9IH6?aa`hf&heX5fK!#ziqoGng|m{gpK}T4F3$6uPdR^c3391(*>Z(&WpOof&EQ(c zb(HH4*Jo~SZY6F@?m+HL?gs7|+#9%0a6jb!$s@$0#pBEq!&A!B$FrR00MAXH&%C_6 zYP=4-QM@I*eY`7q5A)vR{lO>9r^n~Xm%>-iH=A!8-zC0x{G9x%{Eqx_{8jwZ_&4*P z=YJ!>DWE3cERZNrCoo%Jr@(cAuY$sYhJyZrxq>}{s{~I9z7S#;QWJ6&N)c)iS}b%} z=&>-fu(GhTaI$c-@Dkyp!cRrmMASt*MY2S?Mb?U(6Zs%2Bx)iWCR!mnTXe7J12JYX zH8D@I9I<|}O=8!?eu>M9JBp`@cZ#nQza;)$LR!K>B2}VGV!gywiC>ZmlCF~3k`pDj zOWu`YkCS~5X0)iR4^&d7X~m6LUsEs&il zdsz03oS2-0T$bDvxqWgk<%Q+#%2CTwwZRO z_FV0YI;=XDI(a&abgt|2=sN0_>8{p&s3)f9qt~RjUGJT~ihh*-ME&FX{|rnGatxLj z+%^<4^fGKR+-3O5NXsbMXtvQ6V_su7<9g#A#ve_zO;SzfncOfHHuW`aH$7zf*Ua3k z$ZVb2OLGx7WoA`kwaMzewXSux^(yNZHtII1 zHcM6+Ag$xV5eZ0V7I{TfxV)AqWvQKM-IvkDGtjVo;zwfW;w2LeCK4~ROGbP z>ASO)bFK3M7iJfCmoArcu0pP%uCrY4xhc7&yRCNn;BM+(?S8<6)x+CkqQ`YlSnDWSmr7PTbyj{`kcB%?Ydt z;R&k}{v`S*E=v5GNe<}*8Qg^x#v``VegFI z|9xqF=lae1=S^UnkT>DlMEi*=CJ9cen)Gd9#))8`nJ7%iPoG|mxtc+P#XFJVaKSyRx=bZ0zQ|4Zp=P+;GeA)Tk^M5VKTySHd`@(ID z)D}%!%(l2}@v|jiOO7wKSh{kV)Uxhnf0ySif3PBG#gUcfD_5?PS=GOqX?5x9muq6y zTv+R}cE>v1bqm*vt?%Bzu%UFrtBna8uWj<)bZE21=5+39U(OwVjRYkGF;IkR)y z&YPd#al!J!u8TGo_g%8TbnvpvclF}6&}%oY$6SAKBjv`6n>jZ>-YUEG z`*y<});nEy1@2C}Cv$Jnea-vpADBJZ`_Sd#nMc8oZa+?b{Q61plRr;ep7B1L_FVq? z$`?j2_Plg^dEr&`tEaCEUjKg6_EzxioOc@Uw!C+Ef96BPho>KlKK}pI^I7uq@-HS| z4t)*ydhc8Ax8L8pen|dU@zdhx@n7M;p8qcY!~SQ+U(LU}{(1kq^FR0h|NplcoI(8z zMmS(%VrBvX7G`E9W>ywfR#p}k7FITPHda=4HWn5(PBwN95MX8FCftd;9 z8b%HV&i_XkTm=}I7@1g^SQ%N_SXmgEnOPZ`m{|lFScMb~*&Nx0l>!qBMT{CJE>w2f zc<|x}4$+{8AH`IRlbpp>i#R2Mn@p06Fa1BlAjb%`1>73~S;foxc>|av#X=)z90NO>zP;d?+cONw<_rz zJ;Swfd92;F_6^6SH#jUWi@tHY@%Z(>{|X*VV6dv*Z+83a&Lf^(3Td~$wl&$%vlTU3`nxG^i>At?=g@;haTVW>Gx5`YMkBk4_W{p`C`413Ito1WiZe@|HY@Jltr1+r!eZ$DJu{u#ZG+029Mj@avS*Q;&kacx<-X>XLs z#$X*iS^jCfjki6-ZZvN{+J9W;MysZ(Qp?}$fcu8exA3gZpQ*O-bjPU;TC3!9*M!tX zzss$E+P*4g&zZ=>vqE3zg|%|3-IaJVFG?!orf6u}laJN$_5CkvBsCSEF`PYpfA;J= z(|(m5s&CoUgg$yO2}p=-*q)@DczgfDdp~-FCn}nXe3z@)`*jb8LeHVS+1EaP@?Q5! z(Cd}f0zM9#<~twvZ)5x!88l~w&{w-%rLS3Lr-`Jkzwxy~tK*hK7)wv2S7UbB{_XO% z$0a5#nILlY?(3I+TLhRUo%#5oCpa^kiTB&q{$t^mhd$F{$^ZiaeEm#9>u-@3b*Jics+ulde2dud%!nU$Y9-5V=v=I%{s$niImJude{zr>SO#Zu;9ecGiD%m1l9d}Wjw zv@mDEuLkz)=-hwxd*7|sb^U6$=P%n?#-Ny|eY+NK$v8SegVA-H`i2cVEF1(xLe5`b ze)-&nNmm@ytB-q18|yayy0h_44%>m`qne?DfB9cc-%_o}v`n_O^-q$*snj{ z{;lAqOWG;R>rWl`Yj9|BZMFSnAh71vdamOqt?w}Z*=LaV#DL}3t$hJYxVgSw_my$x z+*0MBFo{vsf8X!lwU$5j*-li{b3gg@?`yuO(3002Nt@V%R`qYx%@*DJO}hBwHZ~!d zpsCC6Pd_KBso4FW!G25mx4Nw6B@+*9SNi>*p?-Rfte5HO?|W=iBjYSnIAj_km{0xv zcwBzk{lh$(o&nN1bP zZ^yTIZsG51SxmgGGXJ`teLAD3Pg~4Q z|Mkq_Pn!4EURBUon47fB^~(+6GUc6nw?FQ$F%-HX{HJko^+D0H%c~qtI!fP6Ql6k& z;JbJC{h$B*RW@At?cSsvp)FGVv(QE9#>BI2zZ-AozwhBRYBpNsutVHl|8_=T)~stH z+a|Qd3JE!=baQjR&#%7!a+}o7RMj`En!jIuz9wm4`cU`DE{&oS*Z%16OMiP_`0?%L zr&F3dHkf~TdV1AC;h6_=6H_NW*rIzvrMvg+PkHm_9}Zk#7MLV;ZT|0m#lH{n31=jF*(1#cf+5B3gY?6 zpTFhL-l7z;WbQ0G!%ows7o!dc*0EiCyZ!h3wr4J@r>njD^X>BMLN4AZtDQ`aEnKom zX>H?X?mK_V-XA|Nk>TWc!t!_Kv)}y|AM|eRpTq9Ze{#Zcp34kj2iN(Y{G;RcpW*HY zlc_>W)=mBScBjqPJtEUt)Q+(}b5rJWab4$-8d$hry7<=|%c4&!C+U0byzG#@eZf1FR{N-YI{xfJl zR9)HNAo|O89rt|mYc>-Y&y>7gePHXPYnQmPzEQA_3*R7rBiNM-CS4OZ%>iv zve=`iaiBfwpj3lL5^vMnqkZhgyI9f{51YRJWB+yyZht4%Fwo{Qb8e*N%V$#V)n%3kHyt*Paf!E2xfeE zZR*WW)*W}LS%padnC0;V-ed4maL;5iKQxk{%zmC zeZ%4EIj7F?-@3D{x@|?phUtbbcRO$AZP~uZAS*$1nd+&nk6rg2`~Ku@+WnW=9~(qe z4|wxm{qyG7@BJ1Q^M6_W+gB(Y%Hp_`No_9UmAvgL-7yT-1$h?7cTb6&?fCln>e<%8 z-bbg&Wikjm=k5G-O+iaz1Mdnj~=%t16 z`zEU$4%j!lNPp{Y;r|R4_on`c5IA4W_4Br0Pr=I5tPQrMpW6#pgd{2ZTb35pdR^Zx z_q)%|AoH}&Q=dPxu6%y&(YIZNmp``Z@UG+)dzZ8sJwL-DcWv+abASF;M{K*g zJG}68K`z5A=g8aZEsCEpzkIOq%96F6cj69jT~lXpQR`;=-MyCAr|CM*38&au0?wZ4A}uTKWM#Sht(7it#w z-k7tz-5@T1`rZvKM=vi|uL#-wS?-X+x1KB$xF-5!5Lm)rK;hcyim#`OmE^9$Sdg*^41V9CFB?NY_1xP>z zy4kK5D*k-EMp83_X)EW2C>2$KI|`SJ*ssqq^w7)v^Jn`lk<(eT4^BKe;q}#QftPUX+vcQ-z2O;c5EpRnI9 z-a3qJgG2G3-v=|Fc&hX)zN6cyBJauGm;Xb?(DTDH6T7gjEA zUT%V<+pqrPiTUpzX!=*gZhG;%=HCa_r!2g$KCn47tn1`|uguNB{>@+RN6wk3No$tR zx7(gEfiaRzP&-p=O`M6{1c{hF-TLIuAK!LbluTusaDH|4&rcbiiJj)JS2;~Iomy+u zzCpUXo;&yc?bkJ2QA{3d>yo{C?RSIzDQEhmA zZQlpIk3YYD?(d0en7L$mpIhK}-`)c^Cf>;1_~)36_`_3k&2GHloAYnO!YIQfnx33X z12#onR}p21d3$@(?Z*ei&zOF04Hl06^Ec6>!|TST$t$OB+fbai^POJB^BPXC0L%Qa zCs!|D_FZ+oZKcoS(+hc3qb;4vuO=tgwu{+!{7TqVf=-#uKXWc^`nd7;>D=Q-*XjRw+t%L@*n2zq@_|bXk}cBT%D(DNdH(KA z*+20=1xF@1@*S|hwb!IlkJsv7<$=I%oxE4ycSv>IxOb;k&)!Ee+i613&= z-xm!%m;Btq*)3RrJ}wvrj9besj{T&I_DUKX>kb zSorbtF`o7+uC1%TE!ckR!+!<=hWzF68L!yby)&;Y(S2W3@!(%X(sPS56P-`Jtqiu3d;DB3VVOdAywl%I z9?mneZX3VOzNDg)w7+)!#+|Z%`=7pTJmb9b+`fHtzJ0$ZZmuux%bQZo5}I1^_8IT` z{pYvmZkQ~hCN$MRu{H0$o6X9nLaOSzrQ4VA>~WbAEHX*sO4N}YzTe-z{b!gveOJ)2 zlu1H=X8yH3*cudCUGw_S0SR%Y11A#`a^LBnKX=W3x4t7d`Fk>`#*tA0)G&N2(kJ)z#ecK5@uJ;!Es<@&yl z?GZFK+I{^sr?yOzzHvI!@$8t}J7oA@{zyJKV}Xo;RDDYOzjv-8Q#Iyn{KV$5S1@ZM z)AL;41l{eIdn~#2XR7Sw=AK*nG+xVn%95!@3-^5J6xp}b>}%B;rvAgP-?l3yaup{z zXP*6=S37G%ijTsE*Y}jQ9#=9gYfIm+xORo`iErZD_it13p0#MxlKxuFzy-BmFR7i- z$=n`yNO?(bfQyXLH19U{21hgV`@P$LJo_P*$}(m1)&28&%^6&lJX1e>eT}inugI^b zQYL?&{oVJC+x_15`pZ0LAL+Wx)j$1nt<)?VPvb+)4Tqm!`*3a51c9`k1xdBxY;QMw zxqUy+ZudKbwz@1I@wBX8?{2lPo8_qTi2G@%d(qj)N5ioI^!z#+KS=Q@_V79>FJF{b|3k<%T zaxP!;qb_MmdX@V8D@S{pHmrGaVSC(MiPg^pTZKh=RC8w?$n)*}ms`Kw-p75$6qe@! zt8LxpIo$g9NN)odOXGsAfA@DJ%NV+KwFFx*CtYYW`*mM*bAe~DpjTeq$?!A#9z?0G zs|>98vQ+PR#oT~y4c!gWb9djLe_A?a^&uw?-Q}@a`7&iyZttpoy7aJ?9+R9Yvv%LR zZ`+R_&;7RH;4{@qr#rIW_D^w$Sa|#V>kD7oUoG~yCOD;=g+YLK@9w|$w|Qr6V_bi3 zp8vM?>S2x&V_c=?_n?U-J#w;M?|I7XzSB$yMJ9hx^T&}gbV)rg_kE_Qn-k-mY{q9qJz4Q6oEk5uZWu6{i{I!W`%_{F{Toqz>-rs&e_vbJDuD?Xqvm_ww!ge)X@7lCO z*EYvze=>Xgvi*EOR>-Sw<@-Z;7c{g?iuZ7F<>usDa718bg46rsFK_RYzB|!nr^o*3 ztBq$DwfpcH{dxP!=HG;GMo!k!(V@5QEv{cCDlFjPxubCH_Gh_*&#W&^=v5V6b0S%N zs`-Wcr*GF6|5(Mj;@Y3v@i$vr<2Yu{?B4!ac9P@DFi(c+n2331JM5RAJ%3Efaiy#O z)YrAQpXMg2F4?^7;`y&z`Z~8wti95|{`w31Hc3Oz@E?=o_15z$th(Us5)_u3t7z5t zy!(CPjrx6Z3s$Kf$n&d=56t?#r`oEK_u_Mwu3wRkZb`SC`W`pdzn@d-`f732e+K2V z4~j!%7&#`!x_&Rak*VyV{@eEFoy5+W3m4pbWA>%})C~Q+{T-1#Z}>Xe7U_uGTxZ`e zk>!@pz4N*M{JHIGjQ6}e?#q>QEtSt>gVgSqkL&B-By@U;Fs=VAn{)BWia4IlGaj?f z40g`qv6MgayZBFm5T|eVQHc*vx*qqcg5g7EQ=k8b~GD66`%bvO6d z_?e&O3Pjf};!bMWw&WV8l5h0HeUEQX{`oL~LDGMF)X&4!C*;-O{fa#@YQ2pGCKPe|FubUO0b_ zCZDXs{=Wb?RWSJc^>Y0j0cMB0l};A}qbwtu zrfoaGWG;K|-hYPkEOW|tJpcUZ_tgbn(LKAqZqd+sA*6L;bv1Wh$nDo(|GjNI7s#%- zQnvcN+?m+^x0jux+xop$uc_wK%QN5m{ky&N3W7^NurF4&f@Ie z?aYO@fB(8K7v8nRLTq`gxkjZH+uoc6o^xDxJ%ij=CxpEJ_5SOT&Xx(q;pgK5+*!rd93=S7q64E`E++~u)BYr zozwIApZD+mA2_KrY5ffn*3?+$5-wc-_V@kuXa6%i&nQ}MD!Tmi>@61RM~+-#+IToE z*7bVfPUVjq@Ap5b(_5A;a&fO!)+c`csLnMavxOF9M&G!bn|ObozxBTe&zX&9{`@&_ zZ&lo?)|X%V>-TZT&-qI)hG@t{_paS=C;#vJA`OxGqG#t{TH33<|C;oN36C~RYd^cm z(#m5utN5)yh5IGq&#KRq&A!l|rumWkkZPxMWnSQI&#Pw-su$LGC*G91&wF&{>*{~^ zwkw3cYO%O*M2Op5?w9`ZI&hWXEiK z_fsDZF^6=0eb4meh1rCk)34|s-+KJxx7$4xOEVJ|di`ezuPHI_EB6VBq9C+BDad`rpxE`;~(F)2AZ%PG3#~yeudesGlONC+FkpP9G9ZlCoH%#->&1=OLpM{J73Q+nlvlm^wXpIb9lF8Ze)llyuDu{D{V#l*@w#)?w?w=Z%Xr` zD(0O_T=w?#d{$!k&rt5&Shn5zWBt1yJZDbMU?@7jf8IRb)A!U`1?Kagk$t4J$+Rq+ zK_s^*ci%Pj8~3-rZr@<1()G#KLS}2XB)4Yrir0$;O!bskeh%5dpkemqc5!0fe})f+ zPgIvaE9X!9WHV!~YvrCuPq&n(%yqRj-@BgX#O2DyWSNR;)s>6+PUwr`*ICOzh~*1F_wyH!j!pLfxb`FDq0oX;=`#KE z{yaD_r)}-8=W?yC32RrHEL9{a8S!56@AWw+q!2~{S2Zfh27So-kl zOvloIB{!yqZ3x{d{wwPKato79ye8lEoj?6_NpkI$e zGX8g0uO}U1pE<>8e}=;4d`l4<-8*alGjNx0`_HgWCM0aJa#Y0YbG?6lIy6|_DXo*th)teT?pNq1|k$j!YQpR`9C z)Uxdsa4o5J5;~&&t?&3tjTOC5Cb-{W>6R{kP=D^)@o7y*T`F2vXUUsCPktKlY1M;S zKaZD-3r_t0j#KS;-re{6c%}+1Vpy^5x14WAQq;_aw|`FD;K_92JJ(X(_Z#=!kAA=L zkD+XEamD3VZ*JY5!?I!Ha*J2HW^!w1#d|RJ-xc5X?biLbEpd{WZ2P4D=5D_vlO=J_ za)n{`E8SYBGk%Y^pRYg9=he+~u>bshXF1K~vG#8n_s>|>^y<*JD`rY}%KtOuh#GZx zcq+`ddhf3twXg8WG?m%Tjj~r5wKdg~>Rx`oKfB&9|HKvljtRg1dMtbHweyxl*s1Mt zu@dDn-ZDN1cH|wbulp{h@}chCkDNn`7Omd=)?c@r_j|{yfS}ck+|rR#UhmLdIqzs) z+2iWwwk4~Lmvt5V`MK@4?SggL*F$b`uh8;%b2MavZ|fI<+^yd>iv8SX)6(GJ`sMld zJ1?2|GVHVeT~PQid-b~Y_ths>|GwQ|Z~Z9fP-FQ*k-IzjYQy9Lmi)0T{p~#A^ctO> z{0#}LZ%;nnFVSzBmE^F5wc9G8Yn?KKP>-XuoW-5b=U=w@252W2UdW9J_}rq+8rq~e z)%lcfb8&&K%P01)|8`GU>DqQ*uRv+aqzei)TcVF8vmIzmOfuX5{a2!zu4|Wn{MGsy zmZ@Uz9*@b#Ou)oksq+{A7z)7!T*s%6rzJ(()E$m?zG zcDZYhAJ?ClEOJywtHgBQF-F6rV;gqrTsSHqV9Eu)Ez5n^O}O(UBJ6%`*{cb%UQ1r3aFt(P_kHKKBX9Rj+leG^M)Hj94fE3e?NG;M&j&B^L5u=*Cx4_Pg~i*>7lp$ zs^Sivx=Qmu=O4DtnBP!p@z+DlPV+AZ-QId^-}P_JihsYqUgfa0)oO8q@4C|7w&Q*E z_AgtH82{MwGH&%--o$AeT-j8#l6aagd73fE=l9S1{h%>1`;70G=YM~?Ph0-vZx~C& znt)fVIfp~fo!*~N*ZiFC9K)CPlrtXtEvsp{_HQ;aj!Cbajg>59@_yQ8ou ze|!GZ9}jtsoZ*{xZEJ4TJ(hIOlJ z51Sr6c>lxWIfe`>osFSSB(DFs9Wg=3z2~0YETfB4T{p{is!#CW_wU>BrM_-4EBDR$ z`SWw*qO_>5*EU?R2&oih+;PcUdj4$w-VdfL*8JGN=?nAuD3=A^+hZ1l3rn1u8XBCm z+w|?9{g2rl1GwC`$Eyhh8Z15jPKqUEV$7zyV*eRxec$B2-FNqc(vp>?IyS4nyl~#V z;GWiT}}h^3=Y*hrC+uf#2$}7(b{pa+*c*)0F&pHvM2TDFW>#-5XicE{j|erQZHX~ga(v0 zZri+4WfSjKp-P6i+uv`y_ABvn$b_3AG5OzC{`%ac@=E*q=ZMVQvcwk(*_*oG?j05s)zP@{_pR6b zGP$emoq0yR^%-efoq~mI^z?4cKFz}ViG*Xyyui2KGB$bmt8zpKfCK^h{Qxa2YL4EZ@+fE`Ltz{h_fN9<0S_xzi&I=zQ6zR zwvg9M$!X_)W$!({K*!Q1%FE%@(`~O83S2t&ZG!r=Ro^zt-w!Ik{p{MG+`5NqBCH{k zf@4jV+U)h)x+Zd2Qc$eF|FnA-xmU%Ob>_WdRy$&#^1DtxzRuw0r0h8t?|!-X{>!tE zNghhezHfHsRG;f%Xjn0WY2U&PTi2dHCT&y|P_*iw+xqZMj_I0fKmAj#ZI4Nr6qXw2 zSUpwV{P`raV{Ow+{xjT?j?U7rtqVN0jAcbmdEU0BEj_0`PMsR`(D#H@?K?Z&qyHIt zKPv3VbaM+d!)@ez3A{2#vA!ZcmHRQ@$gV!dHMODeOHuB z3Crt?O|7+8lN4PK-8**V`}5v@i%;?zsl}h}uYbnkeCO-+&{olagPr0mLgx0)_0{po zAK$jA3j}cqabJ77{@S;ea+zJPCVp(#vsP+>R;|{yyJgSkCz<~(c=JMob2t0))0Zb) zlS!Mi{;wl5ulDyDeB}!LZ|bWbhUqob>8s^d8K{}}FXelEaAEL>q< zw(ggcDy!P|_8^vs10nq$u`0K2`M%xu``fSlrjx9xo!_mpe%-&vG%>8vM$KF(LX7#6 zBlE4>eb@ItZ}XhNp%U}!LVlEN<>AYQm)z#oJl;M@d1dk@{(D=0-~0Wb$7@q-@PvB* z_zW+>s)Hi?r|50lx%KG5t#9=Bu08Iq*D=&!f3$2X!_`*LoxcxrB%S-P%%!l_SLqJ- zn{DF%)bHPB=3qT4yX)^mmP<2=mo_sS>=3ib;lI33rKKYJ?<(ojm%cw`m=U;q2z)$sg)@j8a`$&2O(S zZw>F-?I@}zhqVCPeVIPSw=Kbj8n5pAuc_XnGAaLh)WOA9mvXBViau!GcOLpM7n3&R1Hu=kdlBmtHj{>v(nN>DDLy=A9yKTfcw*Azf2v(bh2O=}+dce_N&Ab9C=~eR=&9UJ=&xo;6uI9xA+U zcjQmcpTADj$Y_Px?3#UUw+~-8_@lM@tH)!bS4OQ2>{q{+Z9o1(ui%M~YSX&XsyA`J zUh-+?^O#QbT9~hMqiUVM+0RG+8K%jFPPz7H>+4tFn!Rt#f5&uM{msQI0vtOPr27w7 z=M)^c8Xx*o=--8aEn5}LLX);_;7!Wmd@y(K`TD*a1rF+(s^{n0+N6U&ogOjrRwLBwK$TgEw{Ig@FWa}EqhTHod|K49C=>37`0K09y!G8u0 z)`fY3PTI25p6LB&V9#InpW)sQsqmRDjZxfDe>1+dEj{~^Gc`o2X|v(peYNk;-TwCT z+rgIWp%QyFpZcyj)^_2;_9fh2tKE`&_OC3n?QMPkrhc7haYNDKptsevnQIpYZJzSX zOu&~#f1#6MY;UJ@b*)}KcS6Pt@2Q#BH&^Yc_}rqw(^aa9jLgab;}vSkl8D*I4E!z?)<}kOh#a;>hxN(`>(lDd#)@gG*w;4oca6q`<*iV z6L0g(Tkvke<3)dN*H=5oXx`bv;dZc=@ts)!yV&YvOf0I-sb|VTS7Z~rrx z9T2-Y_v_`;dD{iL^qz!n`|Z@$#^jr|O6~{ZCuM z8yPjLxQjnYz5Lpysle`I&a0@mT9I=}uhLTowr_v>etfzh)T+oHAG=rb*BXzM4KEJN zGg$Fkq~LzR?2S8aZ~M>i{x(bC%79y6zW1kp47#rse@dET>qf?Iy|oTmZ|~33egE~_ z;R#z>m@*D-ySu;aYxBxnw>6^4j10S&!mjOYJNE7Ujz|9)BshapRjS_o6@GS>H>l(L z>x^F}4rLL?uE+bX-7hcpZ~Fl?eFN^S(8lfS_Sy($6+Wt9^;*4oLiE+!0nZ=Q$uGN+ zv-4nN(5ijYe_cN(=HjTui?2hf%byjqkD}KVO}B@3_P&s{Y!&3ul|yeT!JFoYvk>I$q@e z%)fZwj~>x75z|jM`)}WfecC!{vilnIiQPwRPIz@kJ(!psKX=ESHwLLJ-+MyytAEGW zdsKK!m0n%AdGCd+LY1~_sT=R#I==08*k2$uup9ds1t4`K@|WZ^14gx?U8^wPzCJlFR@`*&p|}%S(Kk{L6#)_ovNl zHj9q)%~|;Nc|=c&Dc>ofO8q-g4|vZVyCxm?gXN^7f@IIp@0a>=l(JivOp0FnZ=z_( zRzb;bCdPmh{>!uH9(#PJIwG3S+sCp~&+Z!Q-AiAVpL~=vS*M{?yrc74u%f;~=i2YC z-+sLm+b1EasyKDZyu8O#B9*eD`rk{h+AXp{?P2-Wb+7CGGpv95BiZMu<5`*i49TaH z&shd~l)sSMU#qY$IqLS`4<7#+0>y50$K-y#FC;KwLx|4Vzp^%e`4+KHU3vX_h2Dwn zU-={M%ys#AV&A>{*O`J@Q+++wvhJOKrg=rOlZ*KyliR0wxE8%kRB|&9@J!ItUw!-0 ze}=<+##~b$*_CbGc8w+I+4R3XR$S5n38}G7yLtQWe?I@^z*-lnYlr=IeqQ6ATSkuYYCY znk<&)o3Lv8>g&}XYxnz@MY6DRY3I+CcTdVmtno-Q;XnQUbJJ?=rdwjps#jvyMBlrA z_~RkTlH7fLxzDRQzPhXRi6)8H-+WtS{yc%>=Y}8B4>nEHVt0?v{deJz^Y*w8&1;OU zCT8D=U|#ZvyOw>Q(u#>m@5^q!4BobHUcuKD0!PART^f%q=vnZqY3uf_?~iZ)dVE4- z(1ZP7KQmlv{TZVj9B}B*lB5!^)Ctlrm5y$EBFDLP=b?q%P9B^~v!AD5zcp{;qqRB8 zPg1X|J(R9h55Ied$$t7duAK`Qe#OrZ|GQA+SLr#~NYSZg0gI0A)H?S2uWtT%-k9rG zfAR00zsHSRLCDsGrS3hmmOVx<9*ylzVR4lWsAaKlk_N*yudr zQaYEro8NxpH31=$=3~2fqIc`fjxJ4bd~~}$rRG7FUdSX5J(Hz7dH1Ykn4R7~|1@KW zo7BH4tlQ<|uK$y$C=pm|nph!IwmrYb zR`Ci)wX@K4Q-ie*s_#117Jt81yYD|k zbpFinKvzjy7Q#V4*QbGly5o0}=8ssE?m ziB(yaflVtUukn5T0=xUC<%G7cc<%r7@1BL1nx`o->$C@-`oul&N#oYv_5L}gMhD9L zx0_wR^Yh?@Hbu?Ls}6@}&(643@>}pn^X=}K-w$4EKB`iPwbo-H2xZzy&+FuzHnnEjH zCheOjwP})bY-{V@?K^M$XE?{PsQIDJm1X<()+R2T5_@x7Ys`wUSXGJH`QO(T9)Fx& z-CSqzjJs6#%NMDvdudYKU$4)pRi3<5U{n9R`(pQNc(saWhJO0D^~GlArl^mnBNt9p znDA>yn3d=Kug5#$e2#3)p0;#rtJe*YN&hB(&gv|g63Z2#vG>HI`~8=HCfe)u?32EH zYR<2phghElA9CteoE$TIu4{POAO5G?ZX`Ieww_;HEdI6XX7llBYgfwX9Z_1u(7SQR zzCYE`_0Q^A9at;R`W~(McSufk`_V$K&nD|89gGYv{d>FjUG0B{=Nv&28saRo?^yq3 zH*fLEddBu|!?8EhE;-A2>}UV{=id&=C|BXY_}ZG=m)dW>i16J0U53e2m22H>hTre= zmp`)GFY)P;mq*%_s-O4cW*pM>%DMPSd)<+Y?3}|1=5If4Q}K!NFa68iy5{yjv*Xj2hA{fJ+SU6i{ylr^ez9!EljX~&xhkJY$YYjn z&R=%8{!P-loR3R0_|GqVxx`jjXe;9uo-J*z;e2_ecce4F%U%0d{l4S!Mn_GRs5^Tr zKkp9WII#5BQ{E%*TzY?dG-#b$oo@4vf5+C=z8l}`CT;lsNoLpcJ(58|lCMAgnPbw?%d+)rv&iZzq z4&R;RL#{ziwc6=RzgpF@2o`mf>@nL}!0>6&jk&!?|FK*Do`0O3Lr!7RSGn}_X`TGB zzh1xPn7eq>+`@fZ%@t}7R_EoO6qEh=r)_G}v-{pvd*im1ZK(=N%zuB0>5|9Msqb7H z)dMzel;J%ofA2=uPR&!E&p-eEYkBz00`9*3Gg1}YFRjs!*X+u!6HzaLtu#lfZ8cR%Vg(`}mw?-_?S z99yu{Xmw`@zc;JWtK6eM%)i!GCvdasZcGi&4$h6weJgBmMxx*S^_z?GVF#PDgN2Ox z!)GkIU41{#Tz%)^368iYp&8oE$@nh6}Kn3UOK@L(fI86<-KpN-HrrPK7j{kqc> z_k{cAI^|jAq3rSg`zBP}T$I4JF}a}Z!S9LZ)xRFu##QLG|MOc9uA81(%PxE}D}1_% z;mCHaC6muTz0c(2s8z*VvOM0{*Tpr0|B>C7e~+Iggr>OpD%XbZaL_CNX7B&FMoD1G zgjOb3*HZOYmD#66tD5WYzvg>r7s;gYb=lfO6U^Cr*MHo;Phi)iVr}0)tC!l$zs0iY z;gu+{O=|mfw_0ewV(H5(|JB&GUP{POvGIDWZS-4?nJHHdp3SZK(kK2|$*Sqjnl=BGKqtnXBw4!9c${XK* z*3}6!561fX$L;q>zH{UF(Z-2aG_EX|{Ay?Lk8ghsm>d*6O}?zYch|B-e~R744;<#c zJlF2XnQyyh{XR!PI5^%R=GX0evpYUsSiOFWrs=5@$`9V-TpBMtioH{LcKA z@#zwSYQV#&Psf&f_H5i?x1e0Qe_r|Fkgy<+g|hjd?k!WEExd+jgOH)h3-isgeG|9t z7i>RuZd&%`*<$zKFI&gZs=MIz4}-6=k2fu!vL|9T)1DhOpMUh-5V`bdy4{^Wb8ayN zaTfh8YIRjmbC|zLN6&1>5fHH}|5>_6=|be$`U;b*HPZwQ!ps!?+5R&u z-FYC?aEiy`BHnFZS2O-T?5P~>D0pM*e})g!9%$@8f9ZFfgqCWWbCc)amv5gl2Z-&F zOzsur(%u|npu2);!ur_|_zWImw^gl+}pXxs+n5p(R`|`n4`LahA z9mv~u|M&NQGh7s>L|mVLdi8;j^_*L49!wTGH?d>>8QmX+$6wZ8=ke4m)xTZuc5iCG zcgmGaPGu+dYo|o=css?d<8S}|eo$3Y;r8og7ats%F0eB`;P0Xd-@i^_kh}JCzjZ>9 zF~iY28NQ}%-VSUN)SOtRg|G>%X%Pm?z6J$T9W#;5$c$M^HxZ09`lR6YMONa5wmEo>v?t1BmM;W~mWM);C?z|mxqc+e)&0`_{A}j~OB3^z1(n%D19CTY7vH<{dH;3Z zNz(#7_%hdDpQ^dpa*vW=bdc4isZR5jB|f#+F8^9%ZM;EXM}$Gdq0JG6)w3H9{<#0+ zZO3O--GgQ8Kc9bI5h|d)fBTZ!T-8vf)go74+|c|}yUyTe(W$oD>~ph`Ce1&euYu7>3X{?tv{ z{y3@f*?)%nkDJT0f@0+3_E*VD@m%5B|DO4}%q)|gD)$cdHj3Xa{QLIDLk?BFFPEok zS?+t(&#$R?P;L>| zqW)5+rikXGYw|CD#l&yj`$5G!k}2ruo7=a3HJ^y%ZJ)5^ht#*Ww1B*gS)p7@w*P8) zTzqir`%jfJ55uPAT(PQ(F#Yr8YbTc@i)B&#a~_qcUJd)E_C4&BpMUAVe+IuBMpxqX zKOOe3iwfA{;lvR@&{rUE90P?AHCb9}IJ+Jd3Em6MgwDld*@{_Vw%IBhp?fEjrb>grnrm?mYjx zt0kzI+C~Sy1n?lKXW%6ZI~te=vRIH9u%ei3nKRTe`;MAV}{SF&F<1y#W zdG?)0eCO&5Cq%wH`Es>zz=MltqfCDMVN$8T9sG5zsUvp+p`m1sov_354h9n_wMQvj!BE+mek$*5FpZYW@+@?D&6Gj z8~k(EpA+y6(9x>Dl4Vk_^(iAM_C~e+Ihhr`I?j<>Ufp40uie}E-CicQMMIlU$JT$^ zKi|)Sk|#w!HNWnD^RZRQ`u^mkYcdbdE!8*pzI^-juxk?cl)k=x%i5~<;I!NnqX-87 zy4-y~VnUv9U+LG+b!FeTp*wPe>GieBPYRYcOufJD`MXKiZtxfAb~277-2R2y500Uh^HlMs}G0y1(y-$jXOal?^*CUAx*FeRhF!a^1G3lV!VR zax><>t-F5v{`0>Z&Yxjr^SHL?-Y%{`e-~ulT;Q?Fe?j||JB$Za%KmISaAj)G3dPyC zr+>?2(^@Q1ab!V|{f9Od20iKO_dn+ubk3S0bADQTRptwu*DnR{+Z*#mEL|fpHCsbf zkL~XJvz_1nS=elO#&+L+`ZeX2>7V{RxX3ZrRQ0bz_aFXK{~7K-&^g7xni=@)jok0w z*H}#^)a={vk#EPuPL2IFLU;E$KB@W7ka+oukY8Zju55>C(?WNBZd$dZY=o0a~0e1k9TSfPPSSG>s+q>bNgG9H$%_* z>_c2t5tWTgcH}YcROpue&%j?J!5}-?o6q-5RrNjLJ*y}3*(~wik)d2Gw&U;pY`N#J zTb!XN1Ej$bN_ zn8iGoaGcnrk(m_4ktBA!zxe&%0#nrvA#ZEDt-t>Kd9cE{!m;fepR7lchgANKy>0&) zqRY1l?=$*0eckqd4`z0}T*xQ+E_7)S>w%5G;=b>E{^76;)2$1${yn(;oNJQM`3#=I zxsGdevo&lc=I-17vEOIj$<@85OTV^HI?$UaJn2a6DbqFXsek^>zi&~pRO>tYozL|e z8Ud_5JXLdM`ksyuI=nym_>MmxK7~whD*1Ign)!T2!GDGTg*VrFuWV-6w^VFbbA5mE z-)~2PEjt)OKJWj}FkSik7LK?HrW4}QuDp^ub}on*6|0OPmdm92(<|Va=tmV}5Pl zuHI+P{`h&1wgSK80;`fY{_A!*PioLg&+oaK zajycOp0m&?){Y%-cz?M?z4{0ZzrQv{zg=&s>NzqPez`GwcAlk#)Mgct*UxJ;lA=;CJ~Z_D&M&ul zqq1*fws19%)=7m)Rm(p=eaf*i^M{XCRs-Mi-YZR<8+JbWzWw;?UkRS)I$N06J$}9T z^S%i$gJ#X%V=nwcM>>3(Ez_fe4(}Mu_kG*`C!zZ=)6aIr=hb|N6YOOaBCPi$TkseAg8fEq(% zR_eW{5#1}^<>xQh|Gd$|QE}e>*ljD?Iem=7?q8Af>J%`Yv+L#E-0F!t73$P~?~@3U zYCc-N?fX|g#T5>^ZU*VcAs=CjfelfWn{cDQ{ zi$)V~)z2fjF0C(*)aBmwo$vS1JzMV1e+Ju+;(~2HM<#69<+=FQo>gCey%qlOT;)`k z#5yMCQ!Dxwuqgdrx$yzxLHXW|+>D2&s{EMgrN_UUecAqii2P#15H9sPH{pPk-XmGJ z8pQT3e&0FwRA`X%Yv=m?a}2pWMSr{PW$5(Kc(9o_+&upT-|_sX$4oq;j+%%imgT=V zZZ%6}m7t?X-B|_a6^l+VsP=x@_Mc(dk$)9pCmogEeqTR_@nHIjUye}*jZ<{GU$IPQ zPW}1gKSPdG%u&{S+cX0m8eH%Co!d-KxdTnRFyrk@L(tWeHEnZok3%;(rK7*w? zvm!xLb^F&B`4L5%I1YPA9x%VU?MZ+0{rc-HAx%$pSO0a5IK}k zbMM%H;@SP^8W}BBgM?|OH~yCVx?ji_>MU^N>xEVw->0uXT|cd!Juy4_^S`4XE_WrC z>(t)+=eEe!F!TKMxV?(L>ns^}a@A^a?AwN3m`yU@{H=OJ+X7b!aj7&3UMJX2k=& z83%7nu-Lyr{B6@p1r4FNeKGy<(T64oWf%VrzGSB-;;}uA?bx-gt-Jrl?)`b6B}gcE zhT!>AUp`&=o5;%%zTIwrhsipxV=f)hNsB$L+n`HDAE0DWAfeuJ1kj_WsV>k9iU& zJX746dG+vr1_6VoCv|0oWPO+wq9=)eHj~j4yxe++Kg3tBJpn`Qh$&R=*OZ}gvj&hAlK(Y*WD%WKc4REV|*USru5)A+K5UCXxdhx|O{nGF0Km*$^;%lGc( zUWFAamhL&4C}92K&aLm;Wn}m7{qXg1kVIBpETeCtq}PI#&oc_Y%@7Mq2r;ytRQBU` zlmF)SKa-5)Z|{+)Tg_JP-wHES6^Cuim@jqH41!2Z4dap&Y^8f<3yv2UOAzq@j# zy%>HKI{<77Jd*|!i*H;(bc%^74ge{d`>< zzIX4=L(5`gx(-ad*pVqBA{KD{4)^c+`aZd-78h<9M-CzF2?wg&!S^jD9#P__8DtlW>C1YmXOj5|< zYT)17Cy{2LB`$q??YD0&MzJf}6=x`)%$U`@P%0A76ff>Fb5Ludhvbb?12G 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 JT@R)11ONnn*>V5? literal 0 HcmV?d00001 -- GitLab From 9d8779eebdf0e813748fa1b81b975f443f84f73a Mon Sep 17 00:00:00 2001 From: Rachel Lim Date: Mon, 26 Mar 2018 10:52:46 -0700 Subject: [PATCH 208/906] [tf.data] Usability improvements to `tf.contrib.data.make_csv_dataset`. PiperOrigin-RevId: 190489086 --- .../contrib/data/python/kernel_tests/BUILD | 2 - .../kernel_tests/reader_dataset_ops_test.py | 268 +++++++++++++++--- tensorflow/contrib/data/python/ops/BUILD | 4 + tensorflow/contrib/data/python/ops/readers.py | 250 +++++++++++++--- 4 files changed, 438 insertions(+), 86 deletions(-) diff --git a/tensorflow/contrib/data/python/kernel_tests/BUILD b/tensorflow/contrib/data/python/kernel_tests/BUILD index 8cfe4a727a..d7cc2f14a4 100644 --- a/tensorflow/contrib/data/python/kernel_tests/BUILD +++ b/tensorflow/contrib/data/python/kernel_tests/BUILD @@ -294,9 +294,7 @@ py_test( "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:lib", - "//tensorflow/python:math_ops", "//tensorflow/python:parsing_ops", - "//tensorflow/python:string_ops", "//tensorflow/python:util", "//tensorflow/python/data/ops:iterator_ops", "//third_party/py/numpy", diff --git a/tensorflow/contrib/data/python/kernel_tests/reader_dataset_ops_test.py b/tensorflow/contrib/data/python/kernel_tests/reader_dataset_ops_test.py index 699e8e7865..6ee1b572f1 100644 --- a/tensorflow/contrib/data/python/kernel_tests/reader_dataset_ops_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/reader_dataset_ops_test.py @@ -35,9 +35,7 @@ from tensorflow.python.framework import errors from tensorflow.python.framework import ops from tensorflow.python.lib.io import python_io from tensorflow.python.ops import array_ops -from tensorflow.python.ops import math_ops from tensorflow.python.ops import parsing_ops -from tensorflow.python.ops import string_ops from tensorflow.python.platform import test from tensorflow.python.util import compat @@ -568,12 +566,20 @@ class MakeCsvDatasetTest(test.TestCase): dtypes.int32, dtypes.int64, dtypes.float32, dtypes.float64, dtypes.string ] COLUMNS = ["col%d" % i for i in range(len(COLUMN_TYPES))] + DEFAULT_VALS = [[], [], [], [], ["NULL"]] + DEFAULTS = [ + constant_op.constant([], dtype=dtypes.int32), + constant_op.constant([], dtype=dtypes.int64), + constant_op.constant([], dtype=dtypes.float32), + constant_op.constant([], dtype=dtypes.float64), + constant_op.constant(["NULL"], dtype=dtypes.string) + ] LABEL = COLUMNS[0] def setUp(self): super(MakeCsvDatasetTest, self).setUp() self._num_files = 2 - self._num_records = 7 + self._num_records = 11 self._test_filenames = self._create_files() def _csv_values(self, fileno, recordno): @@ -588,49 +594,63 @@ class MakeCsvDatasetTest(test.TestCase): def _csv_record(self, fileno, recordno): return ",".join(str(v) for v in self._csv_values(fileno, recordno)) + def _create_file(self, fileno, header=True, comment=True): + fn = os.path.join(self.get_temp_dir(), "csv_file%d.csv" % fileno) + f = open(fn, "w") + if header: + f.write(",".join(self.COLUMNS) + "\n") + for recno in range(self._num_records): + f.write(self._csv_record(fileno, recno) + "\n") + if comment: + f.write("# Some comment goes here. Should be ignored!\n") + f.close() + return fn + def _create_files(self): filenames = [] for i in range(self._num_files): - fn = os.path.join(self.get_temp_dir(), "csv_file%d.csv" % i) - filenames.append(fn) - f = open(fn, "w") - f.write(",".join(self.COLUMNS) + "\n") # header line - for j in range(self._num_records): - f.write(self._csv_record(i, j) + "\n") - f.write("# Some comment goes here. Should be ignored!\n") - f.close() + filenames.append(self._create_file(i)) return filenames - def _make_csv_dataset(self, - filenames, - defaults, - label_key=LABEL, - batch_size=1, - num_epochs=1, - shuffle=False, - shuffle_seed=None): + def _make_csv_dataset( + self, + filenames, + defaults, + column_names=COLUMNS, + label_name=LABEL, + batch_size=1, + num_epochs=1, + shuffle=False, + shuffle_seed=None, + header=True, + comment="#", + na_value="", + default_float_type=dtypes.float32, + ): return readers.make_csv_dataset( filenames, - column_keys=self.COLUMNS, - column_defaults=defaults, - label_key=label_key, batch_size=batch_size, + column_names=column_names, + column_defaults=defaults, + label_name=label_name, num_epochs=num_epochs, shuffle=shuffle, shuffle_seed=shuffle_seed, - skip=1, - filter_fn= - lambda line: math_ops.not_equal(string_ops.substr(line, 0, 1), "#"), + header=header, + comment=comment, + na_value=na_value, + default_float_type=default_float_type, ) - def _next_actual_batch(self, file_indices, batch_size, num_epochs): + def _next_actual_batch(self, file_indices, batch_size, num_epochs, defaults): features = {col: list() for col in self.COLUMNS} for _ in range(num_epochs): for i in file_indices: for j in range(self._num_records): values = self._csv_values(i, j) - if not values[-1]: - values[-1] = "NULL" # null values in csv are interpreted as default + for n, v in enumerate(values): + if v == "": # pylint: disable=g-explicit-bool-comparison + values[n] = defaults[n][0] values[-1] = values[-1].encode("utf-8") # Regroup lists by column instead of row @@ -651,7 +671,8 @@ class MakeCsvDatasetTest(test.TestCase): sess, dataset, file_indices, - label_key=LABEL, + defaults=tuple(DEFAULT_VALS), + label_name=LABEL, batch_size=1, num_epochs=1, ): @@ -659,11 +680,11 @@ class MakeCsvDatasetTest(test.TestCase): get_next = iterator.get_next() for expected_features in self._next_actual_batch(file_indices, batch_size, - num_epochs): + num_epochs, defaults): actual_features = sess.run(get_next) - if label_key is not None: - expected_labels = expected_features.pop(label_key) + if label_name is not None: + expected_labels = expected_features.pop(label_name) # Compare labels self.assertAllEqual(expected_labels, actual_features[1]) actual_features = actual_features[0] # Extract features dict from tuple @@ -676,10 +697,7 @@ class MakeCsvDatasetTest(test.TestCase): sess.run(get_next) def test_make_csv_dataset(self): - defaults = [ - constant_op.constant([], dtype=d) for d in self.COLUMN_TYPES[:-1] - ] - defaults.append(constant_op.constant(["NULL"], dtype=dtypes.string)) + defaults = self.DEFAULTS with ops.Graph().as_default() as g: with self.test_session(graph=g) as sess: @@ -705,11 +723,26 @@ class MakeCsvDatasetTest(test.TestCase): self._verify_records( sess, dataset, range(self._num_files), batch_size=2, num_epochs=10) + def test_make_csv_dataset_with_bad_columns(self): + """Tests that exception is raised when input is malformed. + """ + dupe_columns = self.COLUMNS[:-1] + self.COLUMNS[:1] + defaults = self.DEFAULTS + + # Duplicate column names + with self.assertRaises(ValueError): + self._make_csv_dataset( + self._test_filenames, defaults, column_names=dupe_columns) + + # Label key not one of column names + with self.assertRaises(ValueError): + self._make_csv_dataset( + self._test_filenames, defaults, label_name="not_a_real_label") + def test_make_csv_dataset_with_no_label(self): - defaults = [ - constant_op.constant([], dtype=d) for d in self.COLUMN_TYPES[:-1] - ] - defaults.append(constant_op.constant(["NULL"], dtype=dtypes.string)) + """Tests that CSV datasets can be created when no label is specified. + """ + defaults = self.DEFAULTS with ops.Graph().as_default() as g: with self.test_session(graph=g) as sess: # Read from both files. Make sure this works with no label key supplied. @@ -718,16 +751,64 @@ class MakeCsvDatasetTest(test.TestCase): defaults, batch_size=2, num_epochs=10, - label_key=None) + label_name=None) self._verify_records( sess, dataset, range(self._num_files), batch_size=2, num_epochs=10, - label_key=None) + label_name=None) + + def test_make_csv_dataset_with_no_comments(self): + """Tests that datasets can be created from CSV files with no header line. + """ + defaults = self.DEFAULTS + file_without_header = self._create_file( + len(self._test_filenames), comment=False) + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + dataset = self._make_csv_dataset( + file_without_header, + defaults, + batch_size=2, + num_epochs=10, + comment=None, + ) + self._verify_records( + sess, + dataset, + [len(self._test_filenames)], + batch_size=2, + num_epochs=10, + ) + + def test_make_csv_dataset_with_no_header(self): + """Tests that datasets can be created from CSV files with no header line. + """ + defaults = self.DEFAULTS + file_without_header = self._create_file( + len(self._test_filenames), header=False) + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + dataset = self._make_csv_dataset( + file_without_header, + defaults, + batch_size=2, + num_epochs=10, + header=False, + ) + self._verify_records( + sess, + dataset, + [len(self._test_filenames)], + batch_size=2, + num_epochs=10, + ) def test_make_csv_dataset_with_types(self): + """Tests that defaults can be a dtype instead of a Tensor for required vals. + """ defaults = [d for d in self.COLUMN_TYPES[:-1]] defaults.append(constant_op.constant(["NULL"], dtype=dtypes.string)) with ops.Graph().as_default() as g: @@ -735,10 +816,109 @@ class MakeCsvDatasetTest(test.TestCase): dataset = self._make_csv_dataset(self._test_filenames, defaults) self._verify_records(sess, dataset, range(self._num_files)) + def test_make_csv_dataset_with_no_col_names(self): + """Tests that datasets can be created when column names are not specified. + + In that case, we should infer the column names from the header lines. + """ + defaults = self.DEFAULTS + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + # Read from both files. Exercise the `batch` and `num_epochs` parameters + # of make_csv_dataset and make sure they work. + dataset = self._make_csv_dataset( + self._test_filenames, + defaults, + column_names=None, + batch_size=2, + num_epochs=10) + self._verify_records( + sess, dataset, range(self._num_files), batch_size=2, num_epochs=10) + + def test_make_csv_dataset_type_inference(self): + """Tests that datasets can be created when no defaults are specified. + + In that case, we should infer the types from the first N records. + """ + # Test that it works with standard test files (with comments, header, etc) + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + dataset = self._make_csv_dataset( + self._test_filenames, defaults=None, batch_size=2, num_epochs=10) + self._verify_records( + sess, + dataset, + range(self._num_files), + batch_size=2, + num_epochs=10, + defaults=[[], [], [], [], [""]]) + + # Test on a deliberately tricky file + fn = os.path.join(self.get_temp_dir(), "file.csv") + expected_dtypes = [ + dtypes.int32, dtypes.int64, dtypes.float32, dtypes.float32, + dtypes.string, dtypes.string + ] + rows = [[0, 0, 0, "NAN", "", "a"], [1, 2**31 + 1, 2**64, 123, "NAN", ""], + ['"123"', 2, 2**64, 123.4, "NAN", '"cd,efg"']] + expected = [[0, 0, 0, 0, "", "a"], [1, 2**31 + 1, 2**64, 123, "", ""], + [123, 2, 2**64, 123.4, "", "cd,efg"]] + for row in expected: + row[-1] = row[-1].encode("utf-8") # py3 expects byte strings + row[-2] = row[-2].encode("utf-8") # py3 expects byte strings + col_names = ["col%d" % i for i in range(len(expected_dtypes))] + with open(fn, "w") as f: + f.write(",".join(col_names)) + f.write("\n") + for row in rows: + f.write(",".join([str(v) if v else "" for v in row]) + "\n") + + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + dataset = self._make_csv_dataset( + fn, + defaults=None, + column_names=None, + batch_size=1, + num_epochs=1, + label_name=None, + na_value="NAN", + default_float_type=dtypes.float32, + ) + features = dataset.make_one_shot_iterator().get_next() + # Check that types match + for i in range(len(expected_dtypes)): + assert features["col%d" % i].dtype == expected_dtypes[i] + for i in range(len(rows)): + assert sess.run(features) == dict(zip(col_names, expected[i])) + + # With float64 as default type for floats + expected_dtypes = [ + dtypes.int32, dtypes.int64, dtypes.float64, dtypes.float64, + dtypes.string, dtypes.string + ] + with ops.Graph().as_default() as g: + with self.test_session(graph=g) as sess: + dataset = self._make_csv_dataset( + fn, + defaults=None, + column_names=None, + batch_size=1, + num_epochs=1, + label_name=None, + na_value="NAN", + default_float_type=dtypes.float64, + ) + features = dataset.make_one_shot_iterator().get_next() + # Check that types match + for i in range(len(expected_dtypes)): + assert features["col%d" % i].dtype == expected_dtypes[i] + for i in range(len(rows)): + assert sess.run(features) == dict(zip(col_names, expected[i])) + def test_make_csv_dataset_with_shuffle(self): total_records = self._num_files * self._num_records - defaults = [d for d in self.COLUMN_TYPES[:-1]] - defaults.append(constant_op.constant(["NULL"], dtype=dtypes.string)) + defaults = self.DEFAULTS for batch_size in [1, 2]: with ops.Graph().as_default() as g: with self.test_session(graph=g) as sess: diff --git a/tensorflow/contrib/data/python/ops/BUILD b/tensorflow/contrib/data/python/ops/BUILD index 4ecf02825f..647620eb84 100644 --- a/tensorflow/contrib/data/python/ops/BUILD +++ b/tensorflow/contrib/data/python/ops/BUILD @@ -72,14 +72,18 @@ py_library( "//tensorflow/python:dataset_ops_gen", "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", + "//tensorflow/python:lib", + "//tensorflow/python:math_ops", "//tensorflow/python:parsing_ops", "//tensorflow/python:platform", "//tensorflow/python:sparse_tensor", + "//tensorflow/python:string_ops", "//tensorflow/python:tensor_shape", "//tensorflow/python:util", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/ops:readers", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) diff --git a/tensorflow/contrib/data/python/ops/readers.py b/tensorflow/contrib/data/python/ops/readers.py index f70f9c881d..95edca6cdd 100644 --- a/tensorflow/contrib/data/python/ops/readers.py +++ b/tensorflow/contrib/data/python/ops/readers.py @@ -17,6 +17,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import csv + +import numpy as np + from tensorflow.contrib.data.python.ops import interleave_ops from tensorflow.contrib.data.python.ops import shuffle_ops from tensorflow.python.data.ops import dataset_ops @@ -26,8 +30,11 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape +from tensorflow.python.lib.io import file_io from tensorflow.python.ops import gen_dataset_ops +from tensorflow.python.ops import math_ops from tensorflow.python.ops import parsing_ops +from tensorflow.python.ops import string_ops from tensorflow.python.platform import gfile from tensorflow.python.util import deprecation @@ -35,21 +42,142 @@ _ACCEPTABLE_CSV_TYPES = (dtypes.float32, dtypes.float64, dtypes.int32, dtypes.int64, dtypes.string) +def _is_valid_int32(str_val): + try: + # Checks equality to prevent int32 overflow + return dtypes.int32.as_numpy_dtype(str_val) == dtypes.int64.as_numpy_dtype( + str_val) + except (ValueError, OverflowError): + return False + + +def _is_valid_int64(str_val): + try: + dtypes.int64.as_numpy_dtype(str_val) + return True + except (ValueError, OverflowError): + return False + + +def _is_valid_float(str_val, float_dtype): + try: + return float_dtype.as_numpy_dtype(str_val) < np.inf + except ValueError: + return False + + +def _infer_type(str_val, na_value, prev_type, float_dtype): + """Given a string, infers its tensor type. + + Infers the type of a value by picking the least 'permissive' type possible, + while still allowing the previous type inference for this column to be valid. + + Args: + str_val: String value to infer the type of. + na_value: Additional string to recognize as a NA/NaN CSV value. + prev_type: Type previously inferred based on values of this column that + we've seen up till now. + float_dtype: Either `tf.float32` or `tf.float64`. Denotes what float type + to parse float strings as. + Returns: + Inferred dtype. + """ + if str_val in ("", na_value): + return prev_type + + if _is_valid_int32(str_val) and prev_type in (None, dtypes.int32): + return dtypes.int32 + + if _is_valid_int64(str_val) and prev_type in (None, dtypes.int32, + dtypes.int64): + return dtypes.int64 + + if _is_valid_float(str_val, float_dtype) and prev_type != dtypes.string: + return float_dtype + + return dtypes.string + + +def _next_csv_row(filenames, num_cols, field_delim, use_quote_delim, header, + comment): + for fn in filenames: + with file_io.FileIO(fn, "r") as f: + rdr = csv.reader( + f, + delimiter=field_delim, + quoting=csv.QUOTE_MINIMAL if use_quote_delim else csv.QUOTE_NONE) + if header: + next(rdr) # Skip header lines + + for csv_row in rdr: + if comment is not None and csv_row[0].startswith(comment): + continue # Skip comment lines + + if len(csv_row) != num_cols: + raise ValueError( + "Problem inferring types: CSV row has different number of fields " + "than expected.") + yield csv_row + + +def _infer_column_defaults(filenames, num_cols, field_delim, use_quote_delim, + na_value, header, comment, float_dtype, + rows_for_inference): + """Infers column types from the first N valid CSV records of files.""" + inferred_types = [None] * num_cols + + for rows_read, csv_row in enumerate( + _next_csv_row(filenames, num_cols, field_delim, use_quote_delim, header, + comment)): + if rows_for_inference is not None and rows_read >= rows_for_inference: + break + for i, str_val in enumerate(csv_row): + inferred_types[i] = _infer_type(str_val, na_value, inferred_types[i], + float_dtype) + + # Replace None's with a default type + inferred_types = [t or dtypes.string for t in inferred_types] + # Default to 0 or '' for null values + return [ + constant_op.constant([0 if t is not dtypes.string else ""], dtype=t) + for t in inferred_types + ] + + +def _infer_column_names(filenames, field_delim, use_quote_delim): + """Infers column names from first rows of files.""" + csv_kwargs = { + "delimiter": field_delim, + "quoting": csv.QUOTE_MINIMAL if use_quote_delim else csv.QUOTE_NONE + } + with file_io.FileIO(filenames[0], "r") as f: + column_names = next(csv.reader(f, **csv_kwargs)) + + for name in filenames[1:]: + with file_io.FileIO(name, "r") as f: + if next(csv.reader(f, **csv_kwargs)) != column_names: + raise ValueError("Files have different column names in the header row.") + return column_names + + def make_csv_dataset( file_pattern, batch_size, - column_keys, - column_defaults, - label_key=None, + column_names=None, + column_defaults=None, + label_name=None, field_delim=",", use_quote_delim=True, - skip=0, - filter_fn=None, + na_value="", + header=True, + comment=None, num_epochs=None, shuffle=True, shuffle_buffer_size=10000, shuffle_seed=None, prefetch_buffer_size=1, + default_float_type=dtypes.float32, + num_rows_for_inference=100, ): """Reads CSV files into a dataset. @@ -63,27 +191,36 @@ def make_csv_dataset( records. See @{tf.gfile.Glob} for pattern rules. batch_size: An int representing the number of consecutive elements of this dataset to combine in a single batch. - column_keys: A list of strings that corresponds to the CSV columns, in - order. One per column of the input record. - column_defaults: A list of default values for the CSV fields. One item per - column of the input record. Each item in the list is either one of the - following dtypes: float32, float64, int32, int64, or string, or a - `Tensor` with one of the aforementioned types. One item per column of - the input record, with either scalar default value for that column if it - is required, or, if the column is required, an empty `Tensor` or a dtype. - label_key: A optional string corresponding to the label column. If provided, - the data for this column is returned as a separate `Tensor` from the - features dictionary, so that the dataset complies with the format expected - by a `tf.Estimator.train` or `tf.Estimator.evaluate` input function. + column_names: An optional list of strings that corresponds to the CSV + columns, in order. One per column of the input record. If this is not + provided, infers the column names from the first row of the records. + These names will be the keys of the features dict of each dataset element. + column_defaults: A optional list of default values for the CSV fields. One + item per column of the input record. Each item in the list is either a + valid CSV dtype (float32, float64, int32, int64, or string), or a + `Tensor` with one of the aforementioned types. The tensor can either be + a scalar default value (if the column is optional), or an empty tensor (if + the column is required). If a dtype is provided instead of a tensor, the + column is also treated as required. If this list is not provided, tries + to infer types based on reading the first num_rows_for_inference rows of + files specified, and assumes all columns are optional, defaulting to `0` + for numeric values and `""` for string values. + label_name: A optional string corresponding to the label column. If + provided, the data for this column is returned as a separate `Tensor` from + the features dictionary, so that the dataset complies with the format + expected by a `tf.Estimator.train` or `tf.Estimator.evaluate` input + function. field_delim: An optional `string`. Defaults to `","`. Char delimiter to separate fields in a record. use_quote_delim: An optional bool. Defaults to `True`. If false, treats double quotation marks as regular characters inside of the string fields. - skip: An integer that corresponds to the number of lines to skip at the - head of each CSV file. Defaults to 0. - filter_fn: A callable function that takes in a CSV string and returns a - boolean that corresponds to whether the record should be included. If - None, does not filter records. + na_value: Additional string to recognize as NA/NaN. + header: A bool that indicates whether the first rows of provided CSV files + correspond to header lines with column names, and should not be included + in the data. + comment: An optional character string that marks lines that should not be + parsed as csv records. If this is provided, all lines that start with + this character will not be parsed. num_epochs: An int specifying the number of times this dataset is repeated. If None, cycles through the dataset forever. shuffle: A bool that indicates whether the input should be shuffled. @@ -94,50 +231,83 @@ def make_csv_dataset( prefetch_buffer_size: An int specifying the number of feature batches to prefetch for performance improvement. Recommended value is the number of batches consumed per training step. + default_float_type: Either `tf.float32` or `tf.float64`. If defaults are + not provided, float-like strings are interpreted to be this type. + num_rows_for_inference: Number of rows of a file to use for type inference + if record_defaults is not provided. If None, reads all the rows of all + the files. Defaults to 100. Returns: A dataset, where each element is a (features, labels) tuple that corresponds to a batch of `batch_size` CSV rows. The features dictionary maps feature column names to `Tensor`s containing the corresponding column data, and labels is a `Tensor` containing the column data for the label column - specified by `label_key`. + specified by `label_name`. + + Raises: + ValueError: If any of the arguments is malformed. """ - filenames = _get_file_names(file_pattern, False) - column_defaults = [ - constant_op.constant([], dtype=x) if x in _ACCEPTABLE_CSV_TYPES else x - for x in column_defaults - ] + filenames = _get_file_names(file_pattern, shuffle) + if comment is not None and len(comment) != 1: + raise ValueError("`comment` arg must be a single-character string or None") + + # Clean arguments; figure out column names and defaults + if column_names is None: + if not header: + raise ValueError("Cannot infer column names without a header line.") + # If column names are not provided, infer from the header lines + column_names = _infer_column_names(filenames, field_delim, use_quote_delim) + if len(column_names) != len(set(column_names)): + raise ValueError("Cannot have duplicate column names.") + + if column_defaults is not None: + column_defaults = [ + constant_op.constant([], dtype=x) if x in _ACCEPTABLE_CSV_TYPES else x + for x in column_defaults + ] + else: + # If column defaults are not provided, infer from records at graph + # construction time + column_defaults = _infer_column_defaults( + filenames, len(column_names), field_delim, use_quote_delim, na_value, + header, comment, default_float_type, num_rows_for_inference) dataset = dataset_ops.Dataset.from_tensor_slices(filenames) - if label_key is not None: - assert label_key in column_keys + if label_name is not None and label_name not in column_names: + raise ValueError("`label_name` provided must be one of the columns.") + + # Define map and filter functions + def filter_fn(line): + return math_ops.not_equal(string_ops.substr(line, 0, 1), comment) def filename_to_dataset(filename): ds = core_readers.TextLineDataset(filename) - if skip > 0: - ds = ds.skip(skip) - if filter_fn is not None: + if header: + ds = ds.skip(1) + if comment is not None: ds = ds.filter(filter_fn) return ds def decode_csv(line): - """Decodes csv line into features. + """Decodes CSV line into features. Args: line: String tensor corresponding to one csv record. Returns: A dictionary of feature names to values for that particular record. If - label_key is provided, extracts the label feature to be returned as the + label_name is provided, extracts the label feature to be returned as the second element of the tuple. """ columns = parsing_ops.decode_csv( line, column_defaults, field_delim=field_delim, - use_quote_delim=use_quote_delim) - features = dict(zip(column_keys, columns)) - if label_key is not None: - label = features.pop(label_key) + use_quote_delim=use_quote_delim, + na_value=na_value, + ) + features = dict(zip(column_names, columns)) + if label_name is not None: + label = features.pop(label_name) return features, label return features @@ -287,7 +457,7 @@ def make_batched_features_dataset(file_pattern, lambda x: parsing_ops.parse_example(x, features), num_parallel_calls=parser_num_threads) - # TODO(rachelim): Add an optional label_key argument for extracting the label + # TODO(rachelim): Add an optional label_name argument for extracting the label # from the features dictionary, to comply with the type expected by the # input_fn to a `tf.Estimator.train` or `tf.Estimator.evaluate` function. dataset = dataset.prefetch(prefetch_buffer_size) -- GitLab From 005b8aa42c273a0152642279d0c57aa9e08ccbe0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 11:05:37 -0700 Subject: [PATCH 209/906] Fixes an issue with calling tf.contrib.seq2seq.dynamic_decode with an extended BasicDecoder which for example returns a tf.contrib.seq2seq.AttentionWrapperState. In this case the internal while-loop fails when trying to store an instance tf.contrib.seq2seq.AttentionWrapperState in the internal TensorArray. PiperOrigin-RevId: 190491787 --- tensorflow/contrib/seq2seq/python/ops/decoder.py | 15 +++++---------- tensorflow/python/ops/rnn.py | 15 ++++++--------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/tensorflow/contrib/seq2seq/python/ops/decoder.py b/tensorflow/contrib/seq2seq/python/ops/decoder.py index f14974b9d5..898493662d 100644 --- a/tensorflow/contrib/seq2seq/python/ops/decoder.py +++ b/tensorflow/contrib/seq2seq/python/ops/decoder.py @@ -30,6 +30,7 @@ from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import rnn +from tensorflow.python.ops import rnn_cell_impl from tensorflow.python.ops import tensor_array_ops from tensorflow.python.ops import variable_scope from tensorflow.python.util import nest @@ -39,6 +40,7 @@ __all__ = ["Decoder", "dynamic_decode"] _transpose_batch_time = rnn._transpose_batch_time # pylint: disable=protected-access +_zero_state_tensors = rnn_cell_impl._zero_state_tensors # pylint: disable=protected-access @six.add_metaclass(abc.ABCMeta) @@ -133,16 +135,8 @@ class Decoder(object): def _create_zero_outputs(size, dtype, batch_size): """Create a zero outputs Tensor structure.""" - def _t(s): - return (s if isinstance(s, ops.Tensor) else constant_op.constant( - tensor_shape.TensorShape(s).as_list(), - dtype=dtypes.int32, - name="zero_suffix_shape")) - def _create(s, d): - return array_ops.zeros( - array_ops.concat( - ([batch_size], _t(s)), axis=0), dtype=d) + return _zero_state_tensors(s, batch_size, d) return nest.map_structure(_create, size, dtype) @@ -212,7 +206,8 @@ def dynamic_decode(decoder, initial_time = constant_op.constant(0, dtype=dtypes.int32) def _shape(batch_size, from_shape): - if not isinstance(from_shape, tensor_shape.TensorShape): + if (not isinstance(from_shape, tensor_shape.TensorShape) or + from_shape.ndims == 0): return tensor_shape.TensorShape(None) else: batch_size = tensor_util.constant_value( diff --git a/tensorflow/python/ops/rnn.py b/tensorflow/python/ops/rnn.py index 42af7f8b27..1dd464d51d 100644 --- a/tensorflow/python/ops/rnn.py +++ b/tensorflow/python/ops/rnn.py @@ -49,24 +49,21 @@ _concat = rnn_cell_impl._concat def _transpose_batch_time(x): - """Transpose the batch and time dimensions of a Tensor. + """Transposes the batch and time dimensions of a Tensor. - Retains as much of the static shape information as possible. + If the input tensor has rank < 2 it returns the original tensor. Retains as + much of the static shape information as possible. Args: - x: A tensor of rank 2 or higher. + x: A Tensor. Returns: x transposed along the first two dimensions. - - Raises: - ValueError: if `x` is rank 1 or lower. """ x_static_shape = x.get_shape() if x_static_shape.ndims is not None and x_static_shape.ndims < 2: - raise ValueError( - "Expected input tensor %s to have rank at least 2, but saw shape: %s" % - (x, x_static_shape)) + return x + x_rank = array_ops.rank(x) x_t = array_ops.transpose( x, array_ops.concat( -- GitLab From 7e4432ca4da28621deb20b8f3ce7cec6aa0e8e67 Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Mon, 26 Mar 2018 11:15:37 -0700 Subject: [PATCH 210/906] BUGFIX: Fix failure-to-broadcast in Wishart.sample. PiperOrigin-RevId: 190493969 --- .../python/kernel_tests/wishart_test.py | 20 +++++++++++++++++++ .../distributions/python/ops/wishart.py | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/distributions/python/kernel_tests/wishart_test.py b/tensorflow/contrib/distributions/python/kernel_tests/wishart_test.py index 9044aa2850..dcecce981f 100644 --- a/tensorflow/contrib/distributions/python/kernel_tests/wishart_test.py +++ b/tensorflow/contrib/distributions/python/kernel_tests/wishart_test.py @@ -390,6 +390,26 @@ class WishartCholeskyTest(test.TestCase): chol_scale, dtype=np.int32), validate_args=False) + def testSampleBroadcasts(self): + dims = 2 + batch_shape = [2, 3] + sample_shape = [2, 1] + scale = np.float32([ + [[1., 0.5], + [0.5, 1.]], + [[0.5, 0.25], + [0.25, 0.75]], + ]) + scale = np.reshape(np.concatenate([scale, scale, scale], axis=0), + batch_shape + [dims, dims]) + wishart = distributions.WishartFull(df=5, scale=scale) + x = wishart.sample(sample_shape, seed=42) + with self.test_session() as sess: + x_ = sess.run(x) + expected_shape = sample_shape + batch_shape + [dims, dims] + self.assertAllEqual(expected_shape, x.shape) + self.assertAllEqual(expected_shape, x_.shape) + if __name__ == "__main__": test.main() diff --git a/tensorflow/contrib/distributions/python/ops/wishart.py b/tensorflow/contrib/distributions/python/ops/wishart.py index e4ac65012b..5a8c94dabf 100644 --- a/tensorflow/contrib/distributions/python/ops/wishart.py +++ b/tensorflow/contrib/distributions/python/ops/wishart.py @@ -228,9 +228,12 @@ class _WishartLinearOperator(distribution.Distribution): # Complexity: O(nbk) # This parametrization is equivalent to Chi2, i.e., # ChiSquared(k) == Gamma(alpha=k/2, beta=1/2) + expanded_df = self.df * array_ops.ones( + self.scale_operator.batch_shape_tensor(), + dtype=self.df.dtype.base_dtype) g = random_ops.random_gamma(shape=[n], alpha=self._multi_gamma_sequence( - 0.5 * self.df, self.dimension), + 0.5 * expanded_df, self.dimension), beta=0.5, dtype=self.dtype, seed=distribution_util.gen_new_seed( -- GitLab From a7588a70a5de8ece6920f4eb8b877104ede898f7 Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Mon, 26 Mar 2018 11:18:26 -0700 Subject: [PATCH 211/906] tf.GradientTape: Clearly say that tf.while_loop and tf.cond are not supported by tf.GradientTape.gradient() at this time. PiperOrigin-RevId: 190494436 --- tensorflow/python/eager/BUILD | 1 + tensorflow/python/eager/backprop.py | 8 ++++ tensorflow/python/eager/backprop_test.py | 44 ++++++++++++++++++++++ tensorflow/python/ops/control_flow_grad.py | 5 +-- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 5bedf9c6fd..0e089a26eb 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -105,6 +105,7 @@ cuda_py_test( ":test", "//tensorflow/python:embedding_ops", "//tensorflow/python:array_ops", + "//tensorflow/python:control_flow_ops", "//tensorflow/python:math_ops", "//tensorflow/python:nn_ops", "//tensorflow/python:resource_variable_ops", diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index 06e11f6ef9..cdcce65c52 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -86,6 +86,14 @@ class _MockOp(object): return make_attr(typ, self.attrs[i + 1]) raise KeyError(attr) + def _get_control_flow_context(self): + raise NotImplementedError( + "tf.GradientTape.gradients() does not support graph control flow " + "operations like tf.cond or tf.while at this time. Use tf.gradients() " + "instead. If you need this feature, please file a feature request at " + "https://github.com/tensorflow/tensorflow/issues/new" + ) + def _magic_gradient_function(op_name, attr_tuple, num_inputs, inputs, outputs, out_grads): diff --git a/tensorflow/python/eager/backprop_test.py b/tensorflow/python/eager/backprop_test.py index bca2928708..f04d89a6d9 100644 --- a/tensorflow/python/eager/backprop_test.py +++ b/tensorflow/python/eager/backprop_test.py @@ -31,6 +31,7 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import custom_gradient from tensorflow.python.ops import embedding_ops from tensorflow.python.ops import gradients @@ -384,6 +385,49 @@ class BackpropTest(test.TestCase): grad = g.gradient(y, [x])[0] self.assertEqual(self.evaluate(grad), 6.0) + @test_util.run_in_graph_and_eager_modes() + def testGradientTapeWithCond(self): + x = constant_op.constant(3.0) + + def true_fn(): + return x + + def false_fn(): + return x * x + + with backprop.GradientTape() as g: + g.watch(x) + y = control_flow_ops.cond(x < x, true_fn, false_fn) + + if not context.executing_eagerly(): + with self.assertRaisesRegexp(NotImplementedError, 'tf.gradients'): + dy = g.gradient(y, [x])[0] + else: + dy = g.gradient(y, [x])[0] + self.assertEqual(self.evaluate(dy), 6.0) + + @test_util.run_in_graph_and_eager_modes() + def testGradientTapeWithWhileLoop(self): + i = constant_op.constant(1) + x = constant_op.constant(2.) + + def cond(i, _): + return i < 3 + + def body(i, x): + return i + 1, x * 2 + + with backprop.GradientTape() as g: + g.watch([x]) + _, y = control_flow_ops.while_loop(cond, body, [i, x]) + + if not context.executing_eagerly(): + with self.assertRaisesRegexp(NotImplementedError, 'tf.gradients'): + dy = g.gradient(y, [x])[0] + else: + dy = g.gradient(y, [x])[0] + self.assertEqual(self.evaluate(dy), 4.0) + @test_util.assert_no_new_tensors def testGradientTapeGradientCalledMultipleTimes(self): with backprop.GradientTape() as g: diff --git a/tensorflow/python/ops/control_flow_grad.py b/tensorflow/python/ops/control_flow_grad.py index 21354b5ae8..45955554ca 100644 --- a/tensorflow/python/ops/control_flow_grad.py +++ b/tensorflow/python/ops/control_flow_grad.py @@ -142,6 +142,7 @@ def _ExitGrad(op, grad): """Gradients for an exit op are calculated using an Enter op.""" graph = ops.get_default_graph() # pylint: disable=protected-access + op_ctxt = op._get_control_flow_context() grad_ctxt = graph._get_control_flow_context() # pylint: enable=protected-access if not grad_ctxt.back_prop: @@ -150,10 +151,8 @@ def _ExitGrad(op, grad): # no gradient computation. return None - # pylint: disable=protected-access - if op._get_control_flow_context().grad_state: + if op_ctxt.grad_state: raise TypeError("Second-order gradient for while loops not supported.") - # pylint: enable=protected-access if isinstance(grad, ops.Tensor): grad_ctxt.AddName(grad.name) -- GitLab From 73937a7096908a9ae01dd7da2d76932a7fed194b Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Mon, 26 Mar 2018 11:38:15 -0700 Subject: [PATCH 212/906] Made the NumElements function more accurate PiperOrigin-RevId: 190497916 --- tensorflow/core/framework/shape_inference.cc | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/framework/shape_inference.cc b/tensorflow/core/framework/shape_inference.cc index 641681973a..54ecaa5dd4 100644 --- a/tensorflow/core/framework/shape_inference.cc +++ b/tensorflow/core/framework/shape_inference.cc @@ -298,13 +298,23 @@ bool InferenceContext::FullyDefined(ShapeHandle s) { DimensionHandle InferenceContext::NumElements(ShapeHandle s) { const auto rank = Rank(s); if (rank == kUnknownRank) return UnknownDim(); + bool found_unknown = false; int64 size = 1; for (int i = 0; i < rank; ++i) { int64 dim_val = Value(Dim(s, i)); - if (dim_val == kUnknownDim) return UnknownDim(); - size *= dim_val; + if (dim_val == kUnknownDim) { + found_unknown = true; + } else if (dim_val == 0) { + return MakeDim(0); + } else { + size *= dim_val; + } + } + if (found_unknown) { + return UnknownDim(); + } else { + return MakeDim(size); } - return MakeDim(size); } string InferenceContext::DebugString(ShapeHandle s) { -- GitLab From 6d46c21e9f300d07e30a2185671f07d34fac3999 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 11:44:19 -0700 Subject: [PATCH 213/906] Make the CSE ("node deduping") pass in ArithmeticOptimizer more robust in the presence of ops that modify their inputs in-place: Do not dedup nodes if the underlying buffers for their outputs may be passed to an in-place op. PiperOrigin-RevId: 190499037 --- tensorflow/core/grappler/op_types.cc | 13 ++++-- tensorflow/core/grappler/op_types.h | 4 ++ .../optimizers/arithmetic_optimizer.cc | 42 ++++++++++++++++--- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 259168bb33..1a6751befc 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -396,12 +396,17 @@ bool IsFreeOfSideEffect(const NodeDef& node) { return false; } } + return !ModifiesInputsInPlace(node); +} + +bool ModifiesInputsInPlace(const NodeDef& node) { // Some nodes do in-place updates on regular tensor inputs. - if (GetBoolAttr(node, "in_place") || GetBoolAttr(node, "inplace") || - StringPiece(op_name).starts_with("Inplace")) { - return false; + string op_name = node.op(); + std::transform(op_name.begin(), op_name.end(), op_name.begin(), ::tolower); + if (StringPiece(op_name).contains("inplace")) { + return true; } - return true; + return GetBoolAttr(node, "in_place") || GetBoolAttr(node, "inplace"); } bool ModifiesFrameInfo(const NodeDef& node) { diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index 49e01f68e3..1ec1cd46e3 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -154,8 +154,12 @@ bool IsCommutative(const NodeDef& node); bool IsPersistent(const NodeDef& node); bool IsFreeOfSideEffect(const NodeDef& node); + bool ModifiesFrameInfo(const NodeDef& node); +// Returns true if the op is known to write to one or more of its inputs. +bool ModifiesInputsInPlace(const NodeDef& node); + // Returns true if the op is an element-wise involution, i.e. if it is its // own inverse such that f(f(x)) == x. bool IsInvolution(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index bc004df608..23e21855c8 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -1085,6 +1085,24 @@ bool ArithmeticOptimizer::OptimizedNodeExists(const NodeDef& node, return node_map_->NodeExists(OptimizedNodeName(node, suffix)); } +namespace { + +bool FeedsInPlaceOp(const SimpleGraphView& graph_view, const NodeDef& node) { + const std::unordered_set op_types_to_traverse = { + node.op(), "Identity", "IdentityN", "Reshape"}; + int node_idx = graph_view.index(node.name()); + std::set node_fanout; + graph_view.DepthFirstSearch(op_types_to_traverse, node_idx, &node_fanout); + for (int fanout : node_fanout) { + if (ModifiesInputsInPlace(graph_view.graph()->node(fanout))) { + return true; + } + } + return false; +} + +} // namespace + bool ArithmeticOptimizer::CanDedup(const NodeDef& node) const { if (nodes_to_preserve_.find(node.name()) != nodes_to_preserve_.end()) { return false; @@ -1104,6 +1122,11 @@ bool ArithmeticOptimizer::CanDedup(const NodeDef& node) const { void ArithmeticOptimizer::DedupComputations() { bool stop = true; + SimpleGraphView graph_view; + if (!graph_view.Initialize(*optimized_graph_).ok()) { + LOG(WARNING) << "Failed to build SimpleGraphView."; + return; + } std::set duplicates; do { stop = true; @@ -1120,19 +1143,28 @@ void ArithmeticOptimizer::DedupComputations() { if (rep == node) { continue; } + // If either node feeds an inplace op, deduping them may cause data races. + // For example: If we dedup nodes initializing two independent inplace + // accumulations, they will write to the same buffer, clobbering each + // other's results. + if (FeedsInPlaceOp(graph_view, *rep) || + FeedsInPlaceOp(graph_view, *node)) { + continue; + } const std::set& fanouts = node_map_->GetOutputs(node->name()); for (NodeDef* fanout : fanouts) { - for (string& name : *fanout->mutable_input()) { + for (int i = 0; i < fanout->input_size(); ++i) { + string* name = fanout->mutable_input(i); int position; - const string nodename = ParseNodeName(name, &position); + const string nodename = ParseNodeName(*name, &position); if (nodename == node->name()) { // Update name in-place. if (position > 0) { - name = StrCat(rep->name(), ":", position); + *name = StrCat(rep->name(), ":", position); } else if (position == 0) { - name = rep->name(); + *name = rep->name(); } else { - name = StrCat("^", rep->name()); + *name = StrCat("^", rep->name()); } node_map_->AddOutput(rep->name(), fanout->name()); } -- GitLab From 3fbdba0c84941f34782a5e074b691916bca61a93 Mon Sep 17 00:00:00 2001 From: Sami Kama Date: Mon, 26 Mar 2018 11:49:03 -0700 Subject: [PATCH 214/906] update GPU installation instructions --- tensorflow/docs_src/install/install_linux.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index 378946b459..3c5db9bced 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -33,7 +33,7 @@ must be installed on your system: * CUDA® Toolkit 9.0. For details, see [NVIDIA's documentation](http://docs.nvidia.com/cuda/cuda-installation-guide-linux/#axzz4VZnqTJ2A). - Ensure that you append the relevant Cuda pathnames to the + Ensure that you append the relevant CUDA pathnames to the `LD_LIBRARY_PATH` environment variable as described in the NVIDIA documentation. * The NVIDIA drivers associated with CUDA Toolkit 9.0. @@ -56,7 +56,7 @@ must be installed on your system: and add its path to your `LD_LIBRARY_PATH` environment variable:

-    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/extras/CUPTI/lib64
+    $ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/usr/local/cuda/extras/CUPTI/lib64
     
For CUDA Toolkit <= 7.5 do: @@ -64,6 +64,16 @@ must be installed on your system:
     $ sudo apt-get install libcupti-dev
     
+ * **[OPTIONAL]** For optimized inferencing performance, you can also install + NVIDIA TensorRT 3.0. For details, see + [NVIDIA's TensorRT documentation](http://docs.nvidia.com/deeplearning/sdk/tensorrt-install-guide/index.html#installing-tar). + Only steps 1-4 in the TensorRT Tar File installation instructions are + required for compatibility with TensorFlow; the Python package installation + in steps 5 and 6 can be omitted. Detailed installation instructions can be found at [package documentataion](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/tensorrt#installing-tensorrt-304) + + **IMPORTANT:** For compatibility with the pre-built `tensorflow-gpu` + package, please use the Ubuntu **14.04** tar file package of TensorRT + even when installing onto an Ubuntu 16.04 system. If you have an earlier version of the preceding packages, please upgrade to the specified versions. If upgrading is not possible, then you may still run -- GitLab From d2604f8dcb8a63ca063f712c24ce5aa63403b0aa Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 11:47:50 -0700 Subject: [PATCH 215/906] Revert to initializing number of threads when SetNumThreads is called. Requiring it to happen before OpInit() is way too confusing for users. PiperOrigin-RevId: 190499644 --- tensorflow/contrib/lite/BUILD | 2 +- tensorflow/contrib/lite/interpreter.cc | 6 ++++++ tensorflow/contrib/lite/kernels/conv.cc | 5 ++--- tensorflow/contrib/lite/kernels/eigen_support.cc | 7 +++++++ tensorflow/contrib/lite/kernels/eigen_support.h | 3 +++ tensorflow/contrib/lite/kernels/gemm_support.cc | 6 ++++++ tensorflow/contrib/lite/kernels/gemm_support.h | 3 +++ 7 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/lite/BUILD b/tensorflow/contrib/lite/BUILD index dafe6f136e..18efa64507 100644 --- a/tensorflow/contrib/lite/BUILD +++ b/tensorflow/contrib/lite/BUILD @@ -133,10 +133,10 @@ cc_library( ":schema_fbs_version", ":simple_memory_arena", ":util", + "//tensorflow/contrib/lite/kernels:eigen_support", "//tensorflow/contrib/lite/kernels:gemm_support", "//tensorflow/contrib/lite/nnapi:nnapi_lib", "//tensorflow/contrib/lite/schema:schema_fbs", - "//tensorflow/core:lib_platform", ], ) diff --git a/tensorflow/contrib/lite/interpreter.cc b/tensorflow/contrib/lite/interpreter.cc index 937c185b0a..4575fe884d 100644 --- a/tensorflow/contrib/lite/interpreter.cc +++ b/tensorflow/contrib/lite/interpreter.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/contrib/lite/context.h" #include "tensorflow/contrib/lite/error_reporter.h" #include "tensorflow/contrib/lite/graph_info.h" +#include "tensorflow/contrib/lite/kernels/eigen_support.h" #include "tensorflow/contrib/lite/kernels/gemm_support.h" #include "tensorflow/contrib/lite/memory_planner.h" #include "tensorflow/contrib/lite/nnapi_delegate.h" @@ -762,6 +763,11 @@ void Interpreter::UseNNAPI(bool enable) { void Interpreter::SetNumThreads(int num_threads) { context_.recommended_num_threads = num_threads; + + // TODO(ahentz): find a way to avoid this. It causes gemmlowp and eigen to + // be required in order to compile the framework. + gemm_support::SetNumThreads(&context_, num_threads); + eigen_support::SetNumThreads(&context_, num_threads); } TfLiteStatus Interpreter::ModifyGraphWithDelegate(TfLiteDelegate* delegate, diff --git a/tensorflow/contrib/lite/kernels/conv.cc b/tensorflow/contrib/lite/kernels/conv.cc index e0cd12f1b4..18ff33bf9f 100644 --- a/tensorflow/contrib/lite/kernels/conv.cc +++ b/tensorflow/contrib/lite/kernels/conv.cc @@ -89,9 +89,6 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { auto* data = new OpData; gemm_support::IncrementUsageCounter(context); eigen_support::IncrementUsageCounter(context); - - data->run_multithreaded_kernel = context->recommended_num_threads != 1; - return data; } @@ -176,6 +173,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { auto* params = reinterpret_cast(node->builtin_data); OpData* data = reinterpret_cast(node->user_data); + data->run_multithreaded_kernel = context->recommended_num_threads != 1; + TF_LITE_ENSURE_STATUS(AllocateTemporaryTensorsIfRequired(context, node)); bool hasBias = node->inputs->size == 3; diff --git a/tensorflow/contrib/lite/kernels/eigen_support.cc b/tensorflow/contrib/lite/kernels/eigen_support.cc index 213e465552..f1fdb42624 100644 --- a/tensorflow/contrib/lite/kernels/eigen_support.cc +++ b/tensorflow/contrib/lite/kernels/eigen_support.cc @@ -46,8 +46,15 @@ void DecrementUsageCounter(TfLiteContext* context) { } if (--ptr->num_references == 0) { delete ptr; + context->eigen_context = nullptr; } } +void SetNumThreads(TfLiteContext* context, int num_threads) { + IncrementUsageCounter(context); + Eigen::setNbThreads(num_threads); + DecrementUsageCounter(context); +} + } // namespace eigen_support } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/eigen_support.h b/tensorflow/contrib/lite/kernels/eigen_support.h index d47e691123..aa8c351fd8 100644 --- a/tensorflow/contrib/lite/kernels/eigen_support.h +++ b/tensorflow/contrib/lite/kernels/eigen_support.h @@ -28,6 +28,9 @@ void IncrementUsageCounter(TfLiteContext* context); // usages all temporary Eigen objects will be deleted. void DecrementUsageCounter(TfLiteContext* context); +// Set the number of threads that can be used by Eigen. +void SetNumThreads(TfLiteContext* context, int num_threads); + } // namespace eigen_support } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/gemm_support.cc b/tensorflow/contrib/lite/kernels/gemm_support.cc index 76a5165d14..95f45ea768 100644 --- a/tensorflow/contrib/lite/kernels/gemm_support.cc +++ b/tensorflow/contrib/lite/kernels/gemm_support.cc @@ -61,5 +61,11 @@ gemmlowp::GemmContext* GetFromContext(TfLiteContext* context) { return ptr->gemm_context_; } +void SetNumThreads(TfLiteContext* context, int num_threads) { + IncrementUsageCounter(context); + GetFromContext(context)->set_max_num_threads(num_threads); + DecrementUsageCounter(context); +} + } // namespace gemm_support } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/gemm_support.h b/tensorflow/contrib/lite/kernels/gemm_support.h index 37af772c68..f033501cb6 100644 --- a/tensorflow/contrib/lite/kernels/gemm_support.h +++ b/tensorflow/contrib/lite/kernels/gemm_support.h @@ -45,6 +45,9 @@ void IncrementUsageCounter(TfLiteContext* context); // 'context'. If there are no more usages the GemmContext will be deleted. void DecrementUsageCounter(TfLiteContext* context); +// Set the number of threads that can be used by gemmlowp. +void SetNumThreads(TfLiteContext* context, int num_threads); + } // namespace gemm_support } // namespace tflite -- GitLab From f9cfb9e917c8937152b248c300b095798d79501a Mon Sep 17 00:00:00 2001 From: Mingsheng Hong Date: Mon, 26 Mar 2018 11:50:19 -0700 Subject: [PATCH 216/906] Extended experimental C API with MNIST dataset/iterators support. PiperOrigin-RevId: 190500020 --- tensorflow/c/c_api_experimental.cc | 1151 ++++++++++++++++++++++- tensorflow/c/c_api_experimental.h | 11 +- tensorflow/c/c_api_experimental_test.cc | 4 +- 3 files changed, 1149 insertions(+), 17 deletions(-) diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc index 1c809cb21e..f411efc941 100644 --- a/tensorflow/c/c_api_experimental.cc +++ b/tensorflow/c/c_api_experimental.cc @@ -138,7 +138,7 @@ static std::vector CreateFunctionsFromTextProto( return {}; } std::vector ret; - for (const auto& fdef : fdef_lib.function()) { + for (const FunctionDef& fdef : fdef_lib.function()) { // Make a copy so that we can mutate it. FunctionDef fdef_to_load = fdef; if (mutate_proto_func) { @@ -148,8 +148,8 @@ static std::vector CreateFunctionsFromTextProto( std::vector binary_proto_buf(fdef_to_load.ByteSizeLong()); fdef_to_load.SerializeToArray(binary_proto_buf.data(), binary_proto_buf.size()); - auto func = TF_FunctionImportFunctionDef(binary_proto_buf.data(), - binary_proto_buf.size(), status); + TF_Function* func = TF_FunctionImportFunctionDef( + binary_proto_buf.data(), binary_proto_buf.size(), status); if (!status->status.ok()) return {}; ret.push_back(UniqueFuncPtr(func, TF_DeleteFunction)); } @@ -7120,6 +7120,1130 @@ library { return CreateFunctionsFromTextProto(func_def, &mutate_proto_func, status); } +// On success, returns a set of TF_Function instances encoding a dataset +// node stack that reads an MNIST file dataset from `file_path`, and +// sets `dataset_name` to the created dataset name. The returned functions must +// be deleted by calling TF_DeleteFunction. +static std::vector CreateMNISTDatasetFunctions( + const char* file_path, std::string* dataset_name, TF_Status* status) { + const char* func_def = R"PREFIX( +library { + function { + signature { + name: "tf_map_func_521bfd08" + input_arg { + name: "arg0" + type: DT_STRING + } + output_arg { + name: "truediv" + type: DT_FLOAT + } + description: "A wrapper for Defun that facilitates shape inference." + } + node_def { + name: "DecodeRaw" + op: "DecodeRaw" + input: "arg0" + attr { + key: "little_endian" + value { + b: true + } + } + attr { + key: "out_type" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "Cast" + op: "Cast" + input: "DecodeRaw:output:0" + attr { + key: "DstT" + value { + type: DT_FLOAT + } + } + attr { + key: "SrcT" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "Reshape/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 784 + } + } + } + } + node_def { + name: "Reshape" + op: "Reshape" + input: "Cast:y:0" + input: "Reshape/shape:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "truediv/y" + op: "Const" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + } + float_val: 255.0 + } + } + } + } + node_def { + name: "truediv" + op: "RealDiv" + input: "Reshape:output:0" + input: "truediv/y:output:0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + } + ret { + key: "truediv" + value: "truediv:z:0" + } + } + function { + signature { + name: "tf_map_func_9a08860d" + input_arg { + name: "arg0" + type: DT_STRING + } + output_arg { + name: "ToInt32" + type: DT_INT32 + } + description: "A wrapper for Defun that facilitates shape inference." + } + node_def { + name: "DecodeRaw" + op: "DecodeRaw" + input: "arg0" + attr { + key: "little_endian" + value { + b: true + } + } + attr { + key: "out_type" + value { + type: DT_UINT8 + } + } + } + node_def { + name: "Reshape/shape" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + } + } + } + } + } + } + node_def { + name: "Reshape" + op: "Reshape" + input: "DecodeRaw:output:0" + input: "Reshape/shape:output:0" + attr { + key: "T" + value { + type: DT_UINT8 + } + } + attr { + key: "Tshape" + value { + type: DT_INT32 + } + } + } + node_def { + name: "ToInt32" + op: "Cast" + input: "Reshape:output:0" + attr { + key: "DstT" + value { + type: DT_INT32 + } + } + attr { + key: "SrcT" + value { + type: DT_UINT8 + } + } + } + ret { + key: "ToInt32" + value: "ToInt32:y:0" + } + } + function { + signature { + name: "tf_predicate_7089b845" + input_arg { + name: "arg0" + type: DT_FLOAT + } + input_arg { + name: "arg1" + type: DT_INT32 + } + input_arg { + name: "Equal/Placeholder" + type: DT_INT64 + } + output_arg { + name: "Equal" + type: DT_BOOL + } + description: "A wrapper for Defun that facilitates shape inference." + } + node_def { + name: "Shape" + op: "Shape" + input: "arg0" + attr { + key: "T" + value { + type: DT_FLOAT + } + } + attr { + key: "out_type" + value { + type: DT_INT64 + } + } + } + node_def { + name: "strided_slice/stack" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 0 + } + } + } + } + node_def { + name: "strided_slice/stack_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "strided_slice/stack_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT32 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT32 + tensor_shape { + dim { + size: 1 + } + } + int_val: 1 + } + } + } + } + node_def { + name: "strided_slice" + op: "StridedSlice" + input: "Shape:output:0" + input: "strided_slice/stack:output:0" + input: "strided_slice/stack_1:output:0" + input: "strided_slice/stack_2:output:0" + attr { + key: "Index" + value { + type: DT_INT32 + } + } + attr { + key: "T" + value { + type: DT_INT64 + } + } + attr { + key: "begin_mask" + value { + i: 0 + } + } + attr { + key: "ellipsis_mask" + value { + i: 0 + } + } + attr { + key: "end_mask" + value { + i: 0 + } + } + attr { + key: "new_axis_mask" + value { + i: 0 + } + } + attr { + key: "shrink_axis_mask" + value { + i: 1 + } + } + } + node_def { + name: "Equal" + op: "Equal" + input: "strided_slice:output:0" + input: "Equal/Placeholder" + attr { + key: "T" + value { + type: DT_INT64 + } + } + } + ret { + key: "Equal" + value: "Equal:z:0" + } + } + function { + signature { + name: "_make_dataset_2451e43a" + output_arg { + name: "FilterDataset" + type: DT_VARIANT + } + is_stateful: true + } + node_def { + name: "FixedLengthRecordDataset/filenames" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "$(DATA_DIR)/train-images-idx3-ubyte" + } + } + } + } + node_def { + name: "FixedLengthRecordDataset/header_bytes" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 16 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset/record_bytes" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 784 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset/footer_bytes" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset/buffer_size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 262144 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset" + op: "FixedLengthRecordDataset" + input: "FixedLengthRecordDataset/filenames:output:0" + input: "FixedLengthRecordDataset/header_bytes:output:0" + input: "FixedLengthRecordDataset/record_bytes:output:0" + input: "FixedLengthRecordDataset/footer_bytes:output:0" + input: "FixedLengthRecordDataset/buffer_size:output:0" + } + node_def { + name: "MapDataset" + op: "MapDataset" + input: "FixedLengthRecordDataset:handle:0" + attr { + key: "Targuments" + value { + list { + } + } + } + attr { + key: "f" + value { + func { + name: "tf_map_func_521bfd08" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 784 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1/filenames_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "$(DATA_DIR)/train-labels-idx1-ubyte" + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1/header_bytes_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 8 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1/record_bytes_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 1 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1/footer_bytes_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1/buffer_size_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 262144 + } + } + } + } + node_def { + name: "FixedLengthRecordDataset_1" + op: "FixedLengthRecordDataset" + input: "FixedLengthRecordDataset_1/filenames_1:output:0" + input: "FixedLengthRecordDataset_1/header_bytes_1:output:0" + input: "FixedLengthRecordDataset_1/record_bytes_1:output:0" + input: "FixedLengthRecordDataset_1/footer_bytes_1:output:0" + input: "FixedLengthRecordDataset_1/buffer_size_1:output:0" + } + node_def { + name: "MapDataset_1" + op: "MapDataset" + input: "FixedLengthRecordDataset_1:handle:0" + attr { + key: "Targuments" + value { + list { + } + } + } + attr { + key: "f" + value { + func { + name: "tf_map_func_9a08860d" + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_INT32 + } + } + } + } + node_def { + name: "ZipDataset" + op: "ZipDataset" + input: "MapDataset:handle:0" + input: "MapDataset_1:handle:0" + attr { + key: "N" + value { + i: 2 + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 784 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "CacheDataset/filename" + op: "Const" + attr { + key: "dtype" + value { + type: DT_STRING + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_STRING + tensor_shape { + } + string_val: "" + } + } + } + } + node_def { + name: "CacheDataset" + op: "CacheDataset" + input: "ZipDataset:handle:0" + input: "CacheDataset/filename:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 784 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "RepeatDataset/count" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: -1 + } + } + } + } + node_def { + name: "RepeatDataset" + op: "RepeatDataset" + input: "CacheDataset:handle:0" + input: "RepeatDataset/count:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 784 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "ShuffleDataset/buffer_size_2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 50000 + } + } + } + } + node_def { + name: "ShuffleDataset/seed" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset/seed2" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 0 + } + } + } + } + node_def { + name: "ShuffleDataset" + op: "ShuffleDataset" + input: "RepeatDataset:handle:0" + input: "ShuffleDataset/buffer_size_2:output:0" + input: "ShuffleDataset/seed:output:0" + input: "ShuffleDataset/seed2:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: 784 + } + } + shape { + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + attr { + key: "reshuffle_each_iteration" + value { + b: true + } + } + } + node_def { + name: "BatchDataset/batch_size" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 128 + } + } + } + } + node_def { + name: "BatchDataset" + op: "BatchDataset" + input: "ShuffleDataset:handle:0" + input: "BatchDataset/batch_size:output:0" + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 784 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + } + node_def { + name: "FilterDataset/batch_size_1" + op: "Const" + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "value" + value { + tensor { + dtype: DT_INT64 + tensor_shape { + } + int64_val: 128 + } + } + } + } + node_def { + name: "FilterDataset" + op: "FilterDataset" + input: "BatchDataset:handle:0" + input: "FilterDataset/batch_size_1:output:0" + attr { + key: "Targuments" + value { + list { + type: DT_INT64 + } + } + } + attr { + key: "output_shapes" + value { + list { + shape { + dim { + size: -1 + } + dim { + size: 784 + } + } + shape { + dim { + size: -1 + } + } + } + } + } + attr { + key: "output_types" + value { + list { + type: DT_FLOAT + type: DT_INT32 + } + } + } + attr { + key: "predicate" + value { + func { + name: "tf_predicate_7089b845" + } + } + } + } + ret { + key: "FilterDataset" + value: "FilterDataset:handle:0" + } + } +} +)PREFIX"; + + *dataset_name = "_make_dataset_2451e43a"; + std::function mutate_proto_func = + [dataset_name, file_path](FunctionDef* fdef) { + VLOG(1) << "Processsing function " << fdef->DebugString(); + if (std::string(fdef->signature().name()) != *dataset_name) return; + // Change the input file pattern to `file_path`. + bool found = false; + // `node_def` may be mutated. + for (auto& node_def : *fdef->mutable_node_def()) { + if (node_def.name() != "FixedLengthRecordDataset/filenames" && + node_def.name() != "FixedLengthRecordDataset_1/filenames_1") + continue; + DCHECK_EQ(node_def.op(), "Const"); + DCHECK_GT(node_def.attr().count("value"), 0); + found = true; + // Replace $(DATA_DIR)/foo with /foo + // TODO(hongm): Use StringPiece manipulation for better efficiency. + const std::string cur_value = + node_def.attr().at("value").tensor().string_val(0); + const std::string pattern = "$(DATA_DIR)"; + DCHECK_EQ(cur_value.compare(0, pattern.length(), pattern), 0); + const std::string new_value = + file_path + cur_value.substr(pattern.length()); + VLOG(1) << "Setting the value of node_def " << node_def.name() + << " to " << new_value; + auto* tensor = (*node_def.mutable_attr())["value"].mutable_tensor(); + tensor->clear_string_val(); + tensor->add_string_val(new_value); + } + VLOG(1) << "Rewrote function to " << fdef->DebugString(); + DCHECK(found); + }; + return CreateFunctionsFromTextProto(func_def, &mutate_proto_func, status); +} + // Adds the input functions to `graph`. On success, returns the created // IteratorGetNext node. static TF_Operation* AddDatasetFunctionAndIteratorNodesToGraph( @@ -7209,15 +8333,16 @@ TF_Operation* TF_MakeFakeIteratorGetNextWithDatasets(TF_Graph* graph, return getnext_node; } -TF_Operation* TF_MakeImagenetIteratorGetNextWithDatasets(TF_Graph* graph, - const char* file_path, - int batch_size, - TF_Status* status) { +TF_Operation* TF_MakeFileBasedIteratorGetNextWithDatasets( + TF_Graph* graph, const char* file_path, int batch_size, + unsigned char is_mnist, TF_Status* status) { tensorflow::Status s; std::string dataset_name; const auto& funcs = - CreateImagenetDatasetFunctions(file_path, &dataset_name, status); + is_mnist + ? CreateMNISTDatasetFunctions(file_path, &dataset_name, status) + : CreateImagenetDatasetFunctions(file_path, &dataset_name, status); if (!status->status.ok()) { return nullptr; } @@ -7226,9 +8351,13 @@ TF_Operation* TF_MakeImagenetIteratorGetNextWithDatasets(TF_Graph* graph, // batch_size X 224 X 224 X 3 auto image_shape = tensorflow::TensorShapeProto(); image_shape.add_dim()->set_size(batch_size); - image_shape.add_dim()->set_size(224); - image_shape.add_dim()->set_size(224); - image_shape.add_dim()->set_size(3); + if (is_mnist) { + image_shape.add_dim()->set_size(784); + } else { + image_shape.add_dim()->set_size(224); + image_shape.add_dim()->set_size(224); + image_shape.add_dim()->set_size(3); + } output_shape_list.push_back(image_shape); // batch_size diff --git a/tensorflow/c/c_api_experimental.h b/tensorflow/c/c_api_experimental.h index a9c551d73e..ebcec8176b 100644 --- a/tensorflow/c/c_api_experimental.h +++ b/tensorflow/c/c_api_experimental.h @@ -96,13 +96,16 @@ TF_CAPI_EXPORT extern TF_Operation* TF_MakeFakeIteratorGetNextWithDatasets( TF_Graph* graph, TF_Status* status); // Similar to the above API, except that the returned iterator reads the -// TFRecord files from `file_path`. +// file based dataset from `file_path`. +// If `is_mnist` is 0, the dataset corresponds to ImageNet. // The iterators outputs 2 tensors: -// - A float tensor of shape `batch_size` X 224 X 224 X 3 +// - A float tensor of shape `batch_size` X 784 when `is_mnist` is non-zero, or +// `batch_size` X 224 X 224 X 3 otherwise. // - An int32 tensor of shape `batch_size` // TODO(hongm): Extend the API to allow customization of the nodes created. -TF_CAPI_EXPORT extern TF_Operation* TF_MakeImagenetIteratorGetNextWithDatasets( - TF_Graph* graph, const char* file_path, int batch_size, TF_Status* status); +TF_CAPI_EXPORT extern TF_Operation* TF_MakeFileBasedIteratorGetNextWithDatasets( + TF_Graph* graph, const char* file_path, int batch_size, + unsigned char is_mnist, TF_Status* status); #ifdef __cplusplus } /* end extern "C" */ diff --git a/tensorflow/c/c_api_experimental_test.cc b/tensorflow/c/c_api_experimental_test.cc index 49d64d18bf..30fcfd401d 100644 --- a/tensorflow/c/c_api_experimental_test.cc +++ b/tensorflow/c/c_api_experimental_test.cc @@ -68,8 +68,8 @@ TEST(CAPI_EXPERIMENTAL, ImagenetIteratorGetNext) { tensorflow::testing::TensorFlowSrcRoot(), "c/testdata/tf_record"); VLOG(1) << "data file path is " << file_path; const int batch_size = 64; - TF_Operation* get_next = TF_MakeImagenetIteratorGetNextWithDatasets( - graph, file_path.c_str(), batch_size, s); + TF_Operation* get_next = TF_MakeFileBasedIteratorGetNextWithDatasets( + graph, file_path.c_str(), batch_size, /*is_mnist*/ false, s); ASSERT_EQ(TF_OK, TF_GetCode(s)) << TF_Message(s); CSession csession(graph, s); -- GitLab From af0fe569f48f3d5e8405eab76e14abde3c4e3d36 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 12:14:05 -0700 Subject: [PATCH 217/906] LSTM support: Support fused activation functions in int16 Add ops. PiperOrigin-RevId: 190503823 --- .../kernels/internal/optimized/optimized_ops.h | 17 +++++++++++++++-- .../kernels/internal/reference/reference_ops.h | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h index f7840258ec..d7a0005f27 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h @@ -1660,11 +1660,21 @@ template inline void Add(const int16* input1_data, const Dims<4>& input1_dims, int input1_shift, const int16* input2_data, const Dims<4>& input2_dims, int input2_shift, + int16 output_activation_min, int16 output_activation_max, int16* output_data, const Dims<4>& output_dims) { gemmlowp::ScopedProfilingLabel label("Add/Int16"); // This is a copy of the reference implementation. We do not currently have a // properly optimized version. - static_assert(Ac == FusedActivationFunctionType::kNone, ""); + static_assert(Ac == FusedActivationFunctionType::kNone || + Ac == FusedActivationFunctionType::kRelu || + Ac == FusedActivationFunctionType::kRelu6 || + Ac == FusedActivationFunctionType::kRelu1, + ""); + TFLITE_DCHECK_LE(output_activation_min, output_activation_max); + if (Ac == FusedActivationFunctionType::kNone) { + TFLITE_DCHECK_EQ(output_activation_min, -32768); + TFLITE_DCHECK_EQ(output_activation_max, 32767); + } const int flat_size = RequiredBufferSizeForDims(output_dims); TFLITE_DCHECK_EQ(RequiredBufferSizeForDims(input1_dims), flat_size); @@ -1685,7 +1695,10 @@ inline void Add(const int16* input1_data, const Dims<4>& input1_dims, F0 scaled_input = F0::FromRaw(gemmlowp::RoundingDivideByPOT(shift_input[i], input_shift)); F0 result = gemmlowp::SaturatingAdd(scaled_input, input_ready_scaled); - output_data[i] = result.raw(); + const int16 raw_output = result.raw(); + const int16 clamped_output = std::min( + output_activation_max, std::max(output_activation_min, raw_output)); + output_data[i] = clamped_output; } } diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h index 472ddc60df..ce12fad95d 100644 --- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h @@ -956,8 +956,18 @@ template inline void Add(const int16* input1_data, const Dims<4>& input1_dims, int input1_shift, const int16* input2_data, const Dims<4>& input2_dims, int input2_shift, + int16 output_activation_min, int16 output_activation_max, int16* output_data, const Dims<4>& output_dims) { - static_assert(Ac == FusedActivationFunctionType::kNone, ""); + static_assert(Ac == FusedActivationFunctionType::kNone || + Ac == FusedActivationFunctionType::kRelu || + Ac == FusedActivationFunctionType::kRelu6 || + Ac == FusedActivationFunctionType::kRelu1, + ""); + TFLITE_DCHECK_LE(output_activation_min, output_activation_max); + if (Ac == FusedActivationFunctionType::kNone) { + TFLITE_DCHECK_EQ(output_activation_min, -32768); + TFLITE_DCHECK_EQ(output_activation_max, 32767); + } const int flat_size = RequiredBufferSizeForDims(output_dims); TFLITE_DCHECK_EQ(RequiredBufferSizeForDims(input1_dims), flat_size); @@ -978,7 +988,10 @@ inline void Add(const int16* input1_data, const Dims<4>& input1_dims, F0 scaled_input = F0::FromRaw(gemmlowp::RoundingDivideByPOT(shift_input[i], input_shift)); F0 result = gemmlowp::SaturatingAdd(scaled_input, input_ready_scaled); - output_data[i] = result.raw(); + const int16 raw_output = result.raw(); + const int16 clamped_output = std::min( + output_activation_max, std::max(output_activation_min, raw_output)); + output_data[i] = clamped_output; } } -- GitLab From 04b1e736897505ccf5b483379289d02a274ea586 Mon Sep 17 00:00:00 2001 From: Shanqing Cai Date: Mon, 26 Mar 2018 12:16:34 -0700 Subject: [PATCH 218/906] tfdbg CLI: Allow node exclusion with tensor filters Fixes: #16619 See the referred GitHub issue for details, but users want to be able to skip certain nodes when searching for inf/nans, because some nodes generate inf/nans even in nominal conditions. This CL adds a new optional flag `--filter_exclude_node_names` (or `-fenn` for short), which allows users to do exactly that, by using a regex for node names. RELNOTES: tfdbg CLI: Allow exclusion of nodes by regular expressions during tensor filter-enabled Session runs: see the new flags `--filter_exclude_node_names` (or `-fenn` for short). PiperOrigin-RevId: 190504225 --- .../docs_src/programmers_guide/debugger.md | 16 ++++++ tensorflow/python/debug/cli/analyzer_cli.py | 22 ++++++++- .../python/debug/cli/analyzer_cli_test.py | 26 ++++++++++ tensorflow/python/debug/lib/debug_data.py | 14 +++++- .../python/debug/lib/session_debug_testlib.py | 49 +++++++++++++++++++ .../debug/wrappers/local_cli_wrapper.py | 39 +++++++++++++-- .../debug/wrappers/local_cli_wrapper_test.py | 36 +++++++++++++- 7 files changed, 196 insertions(+), 6 deletions(-) diff --git a/tensorflow/docs_src/programmers_guide/debugger.md b/tensorflow/docs_src/programmers_guide/debugger.md index d1399814ee..d1cd7e7c06 100644 --- a/tensorflow/docs_src/programmers_guide/debugger.md +++ b/tensorflow/docs_src/programmers_guide/debugger.md @@ -155,6 +155,7 @@ Try the following commands at the `tfdbg>` prompt (referencing the code at | | `-n ` | List dumped tensors with names matching given regular-expression pattern. | `lt -n Softmax.*` | | | `-t ` | List dumped tensors with op types matching given regular-expression pattern. | `lt -t MatMul` | | | `-f ` | List only the tensors that pass a registered tensor filter. | `lt -f has_inf_or_nan` | +| | `-f -fenn ` | List only the tensors that pass a registered tensor filter, excluding nodes with names matching the regular expression. | `lt -f has_inf_or_nan` `-fenn .*Sqrt.*` | | | `-s ` | Sort the output by given `sort_key`, whose possible values are `timestamp` (default), `dump_size`, `op_type` and `tensor_name`. | `lt -s dump_size` | | | `-r` | Sort in reverse order. | `lt -r -s dump_size` | | **`pt`** | | **Print value of a dumped tensor.** | | @@ -200,6 +201,7 @@ Try the following commands at the `tfdbg>` prompt (referencing the code at | | `-n` | Execute through the next `Session.run` without debugging, and drop to CLI right before the run after that. | `run -n` | | | `-t ` | Execute `Session.run` `T - 1` times without debugging, followed by a run with debugging. Then drop to CLI right after the debugged run. | `run -t 10` | | | `-f ` | Continue executing `Session.run` until any intermediate tensor triggers the specified Tensor filter (causes the filter to return `True`). | `run -f has_inf_or_nan` | +| | `-f -fenn ` | Continue executing `Session.run` until any intermediate tensor whose node names doesn't match the regular expression triggers the specified Tensor filter (causes the filter to return `True`). | `run -f has_inf_or_nan -fenn .*Sqrt.*` | | | `--node_name_filter ` | Execute the next `Session.run`, watching only nodes with names matching the given regular-expression pattern. | `run --node_name_filter Softmax.*` | | | `--op_type_filter ` | Execute the next `Session.run`, watching only nodes with op types matching the given regular-expression pattern. | `run --op_type_filter Variable.*` | | | `--tensor_dtype_filter ` | Execute the next `Session.run`, dumping only Tensors with data types (`dtype`s) matching the given regular-expression pattern. | `run --tensor_dtype_filter int.*` | @@ -813,6 +815,20 @@ sess.run(b) the constant-folding would not occur and `tfdbg` should show the intermediate tensor dumps. + +**Q**: I am debugging a model that generates unwanted infinities or NaNs. But + there are some nodes in my model that are known to generate infinities + or NaNs in their output tensors even under completely normal conditions. + How can I skip those nodes during my `run -f has_inf_or_nan` actions? + +**A**: Use the `--filter_exclude_node_names` (`-fenn` for short) flag. For + example, if you known you have a node with name matching the regular + expression `.*Sqrt.*` that generates infinities or NaNs regardless + of whether the model is behaving correctly, you can exclude the nodes + from the infinity/NaN-finding runs with the command + `run -f has_inf_or_nan -fenn .*Sqrt.*`. + + **Q**: Is there a GUI for tfdbg? **A**: Yes, the **TensorBoard Debugger Plugin** is the GUI of tfdbg. diff --git a/tensorflow/python/debug/cli/analyzer_cli.py b/tensorflow/python/debug/cli/analyzer_cli.py index 156afdfd4c..9a47cd12b4 100644 --- a/tensorflow/python/debug/cli/analyzer_cli.py +++ b/tensorflow/python/debug/cli/analyzer_cli.py @@ -185,6 +185,15 @@ class DebugAnalyzer(object): type=str, default="", help="List only Tensors passing the filter of the specified name") + ap.add_argument( + "-fenn", + "--filter_exclude_node_names", + dest="filter_exclude_node_names", + type=str, + default="", + help="When applying the tensor filter, exclude node with names " + "matching the regular expression. Applicable only if --tensor_filter " + "or -f is used.") ap.add_argument( "-n", "--node_name_filter", @@ -484,6 +493,10 @@ class DebugAnalyzer(object): Returns: Output text lines as a RichTextLines object. + + Raises: + ValueError: If `--filter_exclude_node_names` is used without `-f` or + `--tensor_filter` being used. """ # TODO(cais): Add annotations of substrings for dumped tensor names, to @@ -520,8 +533,15 @@ class DebugAnalyzer(object): _add_main_menu(output, node_name=None, enable_list_tensors=False) return output - data_to_show = self._debug_dump.find(filter_callable) + data_to_show = self._debug_dump.find( + filter_callable, + exclude_node_names=parsed.filter_exclude_node_names) else: + if parsed.filter_exclude_node_names: + raise ValueError( + "The flag --filter_exclude_node_names is valid only when " + "the flag -f or --tensor_filter is used.") + data_to_show = self._debug_dump.dumped_tensor_data # TODO(cais): Implement filter by lambda on tensor value. diff --git a/tensorflow/python/debug/cli/analyzer_cli_test.py b/tensorflow/python/debug/cli/analyzer_cli_test.py index 6b110fda9e..55231954d1 100644 --- a/tensorflow/python/debug/cli/analyzer_cli_test.py +++ b/tensorflow/python/debug/cli/analyzer_cli_test.py @@ -820,6 +820,32 @@ class AnalyzerCLISimpleMulAddTest(test_util.TensorFlowTestCase): op_type_regex="(Add|MatMul)") check_main_menu(self, out, list_tensors_enabled=False) + def testListTensorWithFilterAndNodeNameExclusionWorks(self): + # First, create and register the filter. + def is_2x1_vector(datum, tensor): + del datum # Unused. + return list(tensor.shape) == [2, 1] + self._analyzer.add_tensor_filter("is_2x1_vector", is_2x1_vector) + + # Use shorthand alias for the command prefix. + out = self._registry.dispatch_command( + "lt", ["-f", "is_2x1_vector", "--filter_exclude_node_names", ".*v.*"]) + + # If the --filter_exclude_node_names were not used, then the matching + # tensors would be: + # - simple_mul_add/v:0 + # - simple_mul_add/v/read:0 + # - simple_mul_add/matmul:0 + # - simple_mul_add/add:0 + # + # With the --filter_exclude_node_names option, only the last two should + # show up in the result. + assert_listed_tensors( + self, + out, ["simple_mul_add/matmul:0", "simple_mul_add/add:0"], + ["MatMul", "Add"], tensor_filter_name="is_2x1_vector") + check_main_menu(self, out, list_tensors_enabled=False) + def testListTensorsFilterNanOrInf(self): """Test register and invoke a tensor filter.""" diff --git a/tensorflow/python/debug/lib/debug_data.py b/tensorflow/python/debug/lib/debug_data.py index 8d355aa27f..8a65ad087b 100644 --- a/tensorflow/python/debug/lib/debug_data.py +++ b/tensorflow/python/debug/lib/debug_data.py @@ -23,6 +23,7 @@ import glob import json import os import platform +import re import numpy as np import six @@ -1411,7 +1412,11 @@ class DebugDumpDir(object): return self._watch_key_to_datum[device_name].get(debug_watch_key, []) - def find(self, predicate, first_n=0, device_name=None): + def find(self, + predicate, + first_n=0, + device_name=None, + exclude_node_names=None): """Find dumped tensor data by a certain predicate. Args: @@ -1430,17 +1435,24 @@ class DebugDumpDir(object): time order) for which the predicate returns True. To return all the `DebugTensotDatum` instances, let first_n be <= 0. device_name: optional device name. + exclude_node_names: Optional regular expression to exclude nodes with + names matching the regular expression. Returns: A list of all `DebugTensorDatum` objects in this `DebugDumpDir` object for which predicate returns True, sorted in ascending order of the timestamp. """ + if exclude_node_names: + exclude_node_names = re.compile(exclude_node_names) matched_data = [] for device in (self._dump_tensor_data if device_name is None else (self._dump_tensor_data[device_name],)): for datum in self._dump_tensor_data[device]: + if exclude_node_names and exclude_node_names.match(datum.node_name): + continue + if predicate(datum, datum.get_tensor()): matched_data.append(datum) diff --git a/tensorflow/python/debug/lib/session_debug_testlib.py b/tensorflow/python/debug/lib/session_debug_testlib.py index f4fac14019..070d9c4cd7 100644 --- a/tensorflow/python/debug/lib/session_debug_testlib.py +++ b/tensorflow/python/debug/lib/session_debug_testlib.py @@ -669,6 +669,55 @@ class SessionDebugTestBase(test_util.TensorFlowTestCase): self.assertEqual(1, len(first_bad_datum)) self.assertEqual(x_name, first_bad_datum[0].node_name) + def testFindInfOrNanWithOpNameExclusion(self): + with session.Session() as sess: + u_name = "testFindInfOrNanWithOpNameExclusion/u" + v_name = "testFindInfOrNanWithOpNameExclusion/v" + w_name = "testFindInfOrNanWithOpNameExclusion/w" + x_name = "testFindInfOrNanWithOpNameExclusion/x" + y_name = "testFindInfOrNanWithOpNameExclusion/y" + z_name = "testFindInfOrNanWithOpNameExclusion/z" + + u_init = constant_op.constant([2.0, 4.0]) + u = variables.Variable(u_init, name=u_name) + v_init = constant_op.constant([2.0, 1.0]) + v = variables.Variable(v_init, name=v_name) + + # Expected output: [0.0, 3.0] + w = math_ops.subtract(u, v, name=w_name) + + # Expected output: [inf, 1.3333] + x = math_ops.div(u, w, name=x_name) + + # Expected output: [nan, 4.0] + y = math_ops.multiply(w, x, name=y_name) + + z = math_ops.multiply(y, y, name=z_name) + + u.initializer.run() + v.initializer.run() + + _, dump = self._debug_run_and_get_dump( + sess, z, + expected_partition_graph_count=self._expected_partition_graph_count) + + # Find all "offending tensors". + bad_data = dump.find(debug_data.has_inf_or_nan, + exclude_node_names=".*/x$") + + # Verify that the nodes with bad values are caught through running find + # on the debug dump. + self.assertEqual(2, len(bad_data)) + # Assert that the node `x` should have been excluded. + self.assertEqual(y_name, bad_data[0].node_name) + self.assertEqual(z_name, bad_data[1].node_name) + + first_bad_datum = dump.find( + debug_data.has_inf_or_nan, first_n=1, exclude_node_names=".*/x$") + + self.assertEqual(1, len(first_bad_datum)) + self.assertEqual(y_name, first_bad_datum[0].node_name) + def _session_run_for_graph_structure_lookup(self): with session.Session(config=no_rewrite_session_config()) as sess: u_name = "testDumpGraphStructureLookup/u" diff --git a/tensorflow/python/debug/wrappers/local_cli_wrapper.py b/tensorflow/python/debug/wrappers/local_cli_wrapper.py index 1465cb7295..c8625655e5 100644 --- a/tensorflow/python/debug/wrappers/local_cli_wrapper.py +++ b/tensorflow/python/debug/wrappers/local_cli_wrapper.py @@ -115,6 +115,7 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): # unavailable (i.e., is None), the run-start CLI will be launched to ask # the user. This is the case, e.g., right before the first run starts. self._active_tensor_filter = None + self._active_filter_exclude_node_names = None self._active_tensor_filter_run_start_response = None self._run_through_times = 1 self._skip_debug = False @@ -148,6 +149,15 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): type=str, default="", help="Run until a tensor in the graph passes the specified filter.") + ap.add_argument( + "-fenn", + "--filter_exclude_node_names", + dest="filter_exclude_node_names", + type=str, + default="", + help="When applying the tensor filter, exclude node with names " + "matching the regular expression. Applicable only if --tensor_filter " + "or -f is used.") ap.add_argument( "--node_name_filter", dest="node_name_filter", @@ -324,9 +334,11 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): debug_dump.set_python_graph(self._sess.graph) passed_filter = None + passed_filter_exclude_node_names = None if self._active_tensor_filter: if not debug_dump.find( - self._tensor_filters[self._active_tensor_filter], first_n=1): + self._tensor_filters[self._active_tensor_filter], first_n=1, + exclude_node_names=self._active_filter_exclude_node_names): # No dumped tensor passes the filter in this run. Clean up the dump # directory and move on. self._remove_dump_root() @@ -334,10 +346,14 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): else: # Some dumped tensor(s) from this run passed the filter. passed_filter = self._active_tensor_filter + passed_filter_exclude_node_names = ( + self._active_filter_exclude_node_names) self._active_tensor_filter = None + self._active_filter_exclude_node_names = None self._prep_debug_cli_for_run_end( - debug_dump, request.tf_error, passed_filter) + debug_dump, request.tf_error, passed_filter, + passed_filter_exclude_node_names) self._run_start_response = self._launch_cli() @@ -358,7 +374,11 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): if os.path.isdir(self._dump_root): shutil.rmtree(self._dump_root) - def _prep_debug_cli_for_run_end(self, debug_dump, tf_error, passed_filter): + def _prep_debug_cli_for_run_end(self, + debug_dump, + tf_error, + passed_filter, + passed_filter_exclude_node_names): """Prepare (but not launch) CLI for run-end, with debug dump from the run. Args: @@ -368,6 +388,9 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): (if any). passed_filter: (None or str) Name of the tensor filter that just passed and caused the preparation of this run-end CLI (if any). + passed_filter_exclude_node_names: (None or str) Regular expression used + with the tensor filter to exclude ops with names matching the regular + expresssion. """ if tf_error: @@ -383,6 +406,9 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): if passed_filter is not None: # Some dumped tensor(s) from this run passed the filter. self._init_command = "lt -f %s" % passed_filter + if passed_filter_exclude_node_names: + self._init_command += (" --filter_exclude_node_names %s" % + passed_filter_exclude_node_names) self._title_color = "red_on_white" self._run_cli = analyzer_cli.create_analyzer_ui( @@ -496,6 +522,11 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): parsed.op_type_filter = parsed.op_type_filter or None parsed.tensor_dtype_filter = parsed.tensor_dtype_filter or None + if parsed.filter_exclude_node_names and not parsed.till_filter_pass: + raise ValueError( + "The --filter_exclude_node_names (or -feon) flag is valid only if " + "the --till_filter_pass (or -f) flag is used.") + if parsed.profile: raise debugger_cli_common.CommandLineExit( exit_token=framework.OnRunStartResponse( @@ -525,6 +556,8 @@ class LocalCLIDebugWrapperSession(framework.BaseDebugWrapperSession): if parsed.till_filter_pass in self._tensor_filters: action = framework.OnRunStartAction.DEBUG_RUN self._active_tensor_filter = parsed.till_filter_pass + self._active_filter_exclude_node_names = ( + parsed.filter_exclude_node_names) self._active_tensor_filter_run_start_response = run_start_response else: # Handle invalid filter name. diff --git a/tensorflow/python/debug/wrappers/local_cli_wrapper_test.py b/tensorflow/python/debug/wrappers/local_cli_wrapper_test.py index 490812c96d..b06fa26a93 100644 --- a/tensorflow/python/debug/wrappers/local_cli_wrapper_test.py +++ b/tensorflow/python/debug/wrappers/local_cli_wrapper_test.py @@ -87,7 +87,11 @@ class LocalCLIDebuggerWrapperSessionForTest( def _prep_cli_for_run_start(self): pass - def _prep_debug_cli_for_run_end(self, debug_dump, tf_error, passed_filter): + def _prep_debug_cli_for_run_end(self, + debug_dump, + tf_error, + passed_filter, + passed_filter_exclude_op_names): self.observers["debug_dumps"].append(debug_dump) self.observers["tf_errors"].append(tf_error) @@ -451,6 +455,36 @@ class LocalCLIDebugWrapperSessionTest(test_util.TensorFlowTestCase): self.assertEqual(2, len(wrapped_sess.observers["debug_dumps"])) self.assertEqual([None, None], wrapped_sess.observers["tf_errors"]) + def testRunTillFilterPassesWithExcludeOpNames(self): + wrapped_sess = LocalCLIDebuggerWrapperSessionForTest( + [["run", "-f", "greater_than_twelve", + "--filter_exclude_node_names", "inc_v.*"], + ["run"], ["run"]], + self.sess, + dump_root=self._tmp_dir) + + def greater_than_twelve(datum, tensor): + del datum # Unused. + return tensor > 12.0 + + # Verify that adding the same tensor filter more than once is tolerated + # (i.e., as if it were added only once). + wrapped_sess.add_tensor_filter("greater_than_twelve", greater_than_twelve) + + # run five times. + wrapped_sess.run(self.inc_v) + wrapped_sess.run(self.inc_v) + wrapped_sess.run(self.inc_v) + wrapped_sess.run(self.inc_v) + + self.assertAllClose(14.0, self.sess.run(self.v)) + + self.assertEqual([1], wrapped_sess.observers["run_start_cli_run_numbers"]) + + # Due to the --filter_exclude_op_names flag, the run-end CLI should show up + # not after run 3, but after run 4. + self.assertEqual([4], wrapped_sess.observers["run_end_cli_run_numbers"]) + def testRunTillFilterPassesWorksInConjunctionWithOtherNodeNameFilter(self): """Test that --.*_filter flags work in conjunction with -f. -- GitLab From 8158adc21db1612c42607dff41c083dd3a435e58 Mon Sep 17 00:00:00 2001 From: Anna R Date: Mon, 26 Mar 2018 12:20:32 -0700 Subject: [PATCH 219/906] Internal change. PiperOrigin-RevId: 190504933 --- tensorflow/python/kernel_tests/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index ece1da0332..dbe1bd437e 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1089,6 +1089,7 @@ cuda_py_test( tags = [ "no_windows", "noasan", + "noguitar", "notap", ], ) -- GitLab From b704d1488d5c15d9e8497843e0bbc667117383ae Mon Sep 17 00:00:00 2001 From: Anna R Date: Mon, 26 Mar 2018 12:40:56 -0700 Subject: [PATCH 220/906] Internal change. PiperOrigin-RevId: 190507631 --- tensorflow/contrib/data/python/kernel_tests/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/data/python/kernel_tests/BUILD b/tensorflow/contrib/data/python/kernel_tests/BUILD index d7cc2f14a4..0b3bf63f79 100644 --- a/tensorflow/contrib/data/python/kernel_tests/BUILD +++ b/tensorflow/contrib/data/python/kernel_tests/BUILD @@ -480,6 +480,7 @@ py_test( tags = [ "manual", "no_oss", + "notap", ], deps = [ "//tensorflow/contrib/data/python/ops:prefetching_ops", -- GitLab From 5890401336c149f49892579bb1a7f4e7c6a52fea Mon Sep 17 00:00:00 2001 From: Igor Ganichev Date: Mon, 26 Mar 2018 12:47:47 -0700 Subject: [PATCH 221/906] Clarify doc strings on gradient methods PiperOrigin-RevId: 190508614 --- tensorflow/python/eager/backprop.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index cdcce65c52..a7837b8a7f 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -172,7 +172,7 @@ def implicit_val_and_grad(f): The wrapped function returns the value and the gradient of f when called with the same arguments. The gradient is with respect to all TFE variables which - have `variable.watch()` called on them by f. + are either trainable or have `variable.watch()` called on them by f. This function is useful when the exact set of variables to differentiate with is not known ahead of time. @@ -249,8 +249,8 @@ def implicit_grad(f): """Returns a function which differentiates f with respect to variables. The wrapped function returns the gradient of f when called with the same - arguments. The gradient is with respect to all TFE variables which have - `variable.watch()` called on them by f. + arguments. The gradient is with respect to all TFE variables which are + either trainable or have `variable.watch()` called on them by f. This function is useful when the exact set of variables to differentiate with is not known ahead of time. @@ -653,10 +653,10 @@ class GradientTape(object): Operations are recorded if they are executed within this context manager and at least one of their inputs is being "watched". - Variables (created by `tf.contrib.eager.Variable` or @{tf.get_variable}) - are automatically watched. Tensors can be manually watched by invoking the - `watch` - method on this context manager. + Trainable variables (created by `tf.contrib.eager.Variable` or + @{tf.get_variable}, trainable=True is default in both cases) are automatically + watched. Tensors can be manually watched by invoking the `watch` method on + this context manager. For example, consider the function `y = x * x`. The gradient at `x = 3.0` can be computed as: -- GitLab From ea644ac0783537a6ac8a2c8a2432829b3db69aeb Mon Sep 17 00:00:00 2001 From: Amit Patankar Date: Mon, 26 Mar 2018 13:05:52 -0700 Subject: [PATCH 222/906] Disabling the state_management_test. For non-pip builds also. --- tensorflow/contrib/timeseries/python/timeseries/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/timeseries/python/timeseries/BUILD b/tensorflow/contrib/timeseries/python/timeseries/BUILD index 64f5cd8357..d72cc1b8a2 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/BUILD @@ -233,6 +233,7 @@ py_test( ], srcs_version = "PY2AND3", tags = [ + "manual", "no_pip", # b/64527635 "no_pip_gpu", # b/63391119 ], -- GitLab From 383ce820e5221511cb57904ebd9c32d42d797ac9 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 13:08:54 -0700 Subject: [PATCH 223/906] Optimized ops, move code to early, common, section so that it can be shared. PiperOrigin-RevId: 190511964 --- .../internal/optimized/optimized_ops.h | 384 +++++++++--------- 1 file changed, 192 insertions(+), 192 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h index d7a0005f27..f08d9d6d57 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h @@ -324,6 +324,198 @@ void Gemm(const Eigen::MatrixBase& lhs, const Eigen::MatrixBase& rhs, } } +#ifdef GEMMLOWP_NEON +// In the common case of batch size 1, a fully-connected node degenerates +// to a matrix*vector product. LSTM cells contain a fully-connected node; +// when quantized, this becomes a special type of GEMV operation where +// the output is 16bit-quantized, thus needs its own special path. +inline void GEMVForLstmCell(const uint8* input_data, const Dims<4>& input_dims, + const uint8* weights_data, + const Dims<4>& weights_dims, + uint8 weights_zero_point, const int32* bias_data, + const Dims<4>& bias_dims, int32 accum_multiplier, + int accum_shift, int16* output_data, + const Dims<4>& output_dims) { + gemmlowp::ScopedProfilingLabel label("GEMVForLstmCell"); + TFLITE_DCHECK(IsPackedWithoutStrides(input_dims)); + TFLITE_DCHECK(IsPackedWithoutStrides(weights_dims)); + TFLITE_DCHECK(IsPackedWithoutStrides(bias_dims)); + TFLITE_DCHECK(IsPackedWithoutStrides(output_dims)); + TFLITE_DCHECK_EQ(ArraySize(output_dims, 1) * ArraySize(output_dims, 2) * + ArraySize(output_dims, 3), + 1); + const int input_size = input_dims.strides[3]; + const int output_size = MatchingArraySize(weights_dims, 1, output_dims, 0); + // This special fast path for quantized LSTM cells does not try to support + // odd sizes that we haven't encountered in any LSTM cell, that would + // require special code (that would go untested until any LSTM cell + // exercises it). We just guard our assumptions about size evenness with + // the following assertions. + TFLITE_DCHECK(!(output_size % 4)); + TFLITE_DCHECK(!(input_size % 8)); + const int32* bias_ptr = bias_data; + int16* output_ptr = output_data; + for (int out = 0; out < output_size; out += 4) { + int32x4_t acc_0 = vdupq_n_s32(0); + int32x4_t acc_1 = vdupq_n_s32(0); + int32x4_t acc_2 = vdupq_n_s32(0); + int32x4_t acc_3 = vdupq_n_s32(0); + const int16x8_t input_offset_vec = vdupq_n_s16(-128); + const int16x8_t weights_offset_vec = vdupq_n_s16(-weights_zero_point); + int in = 0; + // Handle 16 levels of depth at a time. + for (; in <= input_size - 16; in += 16) { + const uint8x16_t input_val_u8 = vld1q_u8(input_data + in); + const uint8* weights_ptr = weights_data + in + out * input_size; + uint8x16_t weights_val_u8_0 = vld1q_u8(weights_ptr + 0 * input_size); + uint8x16_t weights_val_u8_1 = vld1q_u8(weights_ptr + 1 * input_size); + uint8x16_t weights_val_u8_2 = vld1q_u8(weights_ptr + 2 * input_size); + uint8x16_t weights_val_u8_3 = vld1q_u8(weights_ptr + 3 * input_size); + int16x8_t input_val_0, input_val_1; + const uint8x8_t low = vget_low_u8(input_val_u8); + const uint8x8_t high = vget_high_u8(input_val_u8); + input_val_0 = vreinterpretq_s16_u16(vmovl_u8(low)); + input_val_1 = vreinterpretq_s16_u16(vmovl_u8(high)); + input_val_0 = vaddq_s16(input_val_0, input_offset_vec); + input_val_1 = vaddq_s16(input_val_1, input_offset_vec); + int16x8_t weights_val_0_0, weights_val_1_0, weights_val_2_0, + weights_val_3_0; + int16x8_t weights_val_0_1, weights_val_1_1, weights_val_2_1, + weights_val_3_1; + weights_val_0_0 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_0))), + weights_offset_vec); + weights_val_0_1 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_0))), + weights_offset_vec); + weights_val_1_0 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_1))), + weights_offset_vec); + weights_val_1_1 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_1))), + weights_offset_vec); + weights_val_2_0 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_2))), + weights_offset_vec); + weights_val_2_1 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_2))), + weights_offset_vec); + weights_val_3_0 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_3))), + weights_offset_vec); + weights_val_3_1 = vaddq_s16( + vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_3))), + weights_offset_vec); + acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0_0), + vget_low_s16(input_val_0)); + acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1_0), + vget_low_s16(input_val_0)); + acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2_0), + vget_low_s16(input_val_0)); + acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3_0), + vget_low_s16(input_val_0)); + acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0_0), + vget_high_s16(input_val_0)); + acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1_0), + vget_high_s16(input_val_0)); + acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2_0), + vget_high_s16(input_val_0)); + acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3_0), + vget_high_s16(input_val_0)); + acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0_1), + vget_low_s16(input_val_1)); + acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1_1), + vget_low_s16(input_val_1)); + acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2_1), + vget_low_s16(input_val_1)); + acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3_1), + vget_low_s16(input_val_1)); + acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0_1), + vget_high_s16(input_val_1)); + acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1_1), + vget_high_s16(input_val_1)); + acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2_1), + vget_high_s16(input_val_1)); + acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3_1), + vget_high_s16(input_val_1)); + } + // Handle 8 levels of depth at a time. + for (; in < input_size; in += 8) { + const uint8x8_t input_val_u8 = vld1_u8(input_data + in); + const uint8* weights_ptr = weights_data + in + out * input_size; + uint8x8_t weights_val_u8_0 = vld1_u8(weights_ptr + 0 * input_size); + uint8x8_t weights_val_u8_1 = vld1_u8(weights_ptr + 1 * input_size); + uint8x8_t weights_val_u8_2 = vld1_u8(weights_ptr + 2 * input_size); + uint8x8_t weights_val_u8_3 = vld1_u8(weights_ptr + 3 * input_size); + int16x8_t input_val; + input_val = vreinterpretq_s16_u16(vmovl_u8(input_val_u8)); + input_val = vaddq_s16(input_val, input_offset_vec); + int16x8_t weights_val_0, weights_val_1, weights_val_2, weights_val_3; + weights_val_0 = + vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_0)), + weights_offset_vec); + weights_val_1 = + vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_1)), + weights_offset_vec); + weights_val_2 = + vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_2)), + weights_offset_vec); + weights_val_3 = + vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_3)), + weights_offset_vec); + acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0), + vget_low_s16(input_val)); + acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1), + vget_low_s16(input_val)); + acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2), + vget_low_s16(input_val)); + acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3), + vget_low_s16(input_val)); + acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0), + vget_high_s16(input_val)); + acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1), + vget_high_s16(input_val)); + acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2), + vget_high_s16(input_val)); + acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3), + vget_high_s16(input_val)); + } + // Horizontally reduce accumulators + int32x2_t pairwise_reduced_acc_0, pairwise_reduced_acc_1, + pairwise_reduced_acc_2, pairwise_reduced_acc_3; + pairwise_reduced_acc_0 = + vpadd_s32(vget_low_s32(acc_0), vget_high_s32(acc_0)); + pairwise_reduced_acc_1 = + vpadd_s32(vget_low_s32(acc_1), vget_high_s32(acc_1)); + pairwise_reduced_acc_2 = + vpadd_s32(vget_low_s32(acc_2), vget_high_s32(acc_2)); + pairwise_reduced_acc_3 = + vpadd_s32(vget_low_s32(acc_3), vget_high_s32(acc_3)); + const int32x2_t reduced_lo = + vpadd_s32(pairwise_reduced_acc_0, pairwise_reduced_acc_1); + const int32x2_t reduced_hi = + vpadd_s32(pairwise_reduced_acc_2, pairwise_reduced_acc_3); + int32x4_t reduced = vcombine_s32(reduced_lo, reduced_hi); + // Add bias values. + int32x4_t bias_vec = vld1q_s32(bias_ptr); + bias_ptr += 4; + reduced = vaddq_s32(reduced, bias_vec); + int left_shift = accum_shift > 0 ? accum_shift : 0; + int right_shift = accum_shift > 0 ? 0 : -accum_shift; + reduced = vshlq_s32(reduced, vdupq_n_s32(left_shift)); + // Multiply by the fixed-point multiplier. + reduced = vqrdmulhq_n_s32(reduced, accum_multiplier); + // Rounding-shift-right. + using gemmlowp::RoundingDivideByPOT; + reduced = RoundingDivideByPOT(reduced, right_shift); + // Narrow values down to 16 bit signed. + const int16x4_t res16 = vqmovn_s32(reduced); + vst1_s16(output_ptr, res16); + output_ptr += 4; + } +} +#endif + inline void FullyConnected(const float* input_data, const Dims<4>& input_dims, const float* weights_data, const Dims<4>& weights_dims, const float* bias_data, @@ -2478,198 +2670,6 @@ inline void LstmCell(const float* input_data, const Dims<4>& input_dims, output_state_map.tanh(); } -#ifdef GEMMLOWP_NEON -// In the common case of batch size 1, a fully-connected node degenerates -// to a matrix*vector product. LSTM cells contain a fully-connected node; -// when quantized, this becomes a special type of GEMV operation where -// the output is 16bit-quantized, thus needs its own special path. -inline void GEMVForLstmCell(const uint8* input_data, const Dims<4>& input_dims, - const uint8* weights_data, - const Dims<4>& weights_dims, - uint8 weights_zero_point, const int32* bias_data, - const Dims<4>& bias_dims, int32 accum_multiplier, - int accum_shift, int16* output_data, - const Dims<4>& output_dims) { - gemmlowp::ScopedProfilingLabel label("GEMVForLstmCell"); - TFLITE_DCHECK(IsPackedWithoutStrides(input_dims)); - TFLITE_DCHECK(IsPackedWithoutStrides(weights_dims)); - TFLITE_DCHECK(IsPackedWithoutStrides(bias_dims)); - TFLITE_DCHECK(IsPackedWithoutStrides(output_dims)); - TFLITE_DCHECK_EQ(ArraySize(output_dims, 1) * ArraySize(output_dims, 2) * - ArraySize(output_dims, 3), - 1); - const int input_size = input_dims.strides[3]; - const int output_size = MatchingArraySize(weights_dims, 1, output_dims, 0); - // This special fast path for quantized LSTM cells does not try to support - // odd sizes that we haven't encountered in any LSTM cell, that would - // require special code (that would go untested until any LSTM cell - // exercises it). We just guard our assumptions about size evenness with - // the following assertions. - TFLITE_DCHECK(!(output_size % 4)); - TFLITE_DCHECK(!(input_size % 8)); - const int32* bias_ptr = bias_data; - int16* output_ptr = output_data; - for (int out = 0; out < output_size; out += 4) { - int32x4_t acc_0 = vdupq_n_s32(0); - int32x4_t acc_1 = vdupq_n_s32(0); - int32x4_t acc_2 = vdupq_n_s32(0); - int32x4_t acc_3 = vdupq_n_s32(0); - const int16x8_t input_offset_vec = vdupq_n_s16(-128); - const int16x8_t weights_offset_vec = vdupq_n_s16(-weights_zero_point); - int in = 0; - // Handle 16 levels of depth at a time. - for (; in <= input_size - 16; in += 16) { - const uint8x16_t input_val_u8 = vld1q_u8(input_data + in); - const uint8* weights_ptr = weights_data + in + out * input_size; - uint8x16_t weights_val_u8_0 = vld1q_u8(weights_ptr + 0 * input_size); - uint8x16_t weights_val_u8_1 = vld1q_u8(weights_ptr + 1 * input_size); - uint8x16_t weights_val_u8_2 = vld1q_u8(weights_ptr + 2 * input_size); - uint8x16_t weights_val_u8_3 = vld1q_u8(weights_ptr + 3 * input_size); - int16x8_t input_val_0, input_val_1; - const uint8x8_t low = vget_low_u8(input_val_u8); - const uint8x8_t high = vget_high_u8(input_val_u8); - input_val_0 = vreinterpretq_s16_u16(vmovl_u8(low)); - input_val_1 = vreinterpretq_s16_u16(vmovl_u8(high)); - input_val_0 = vaddq_s16(input_val_0, input_offset_vec); - input_val_1 = vaddq_s16(input_val_1, input_offset_vec); - int16x8_t weights_val_0_0, weights_val_1_0, weights_val_2_0, - weights_val_3_0; - int16x8_t weights_val_0_1, weights_val_1_1, weights_val_2_1, - weights_val_3_1; - weights_val_0_0 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_0))), - weights_offset_vec); - weights_val_0_1 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_0))), - weights_offset_vec); - weights_val_1_0 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_1))), - weights_offset_vec); - weights_val_1_1 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_1))), - weights_offset_vec); - weights_val_2_0 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_2))), - weights_offset_vec); - weights_val_2_1 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_2))), - weights_offset_vec); - weights_val_3_0 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(weights_val_u8_3))), - weights_offset_vec); - weights_val_3_1 = vaddq_s16( - vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(weights_val_u8_3))), - weights_offset_vec); - acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0_0), - vget_low_s16(input_val_0)); - acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1_0), - vget_low_s16(input_val_0)); - acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2_0), - vget_low_s16(input_val_0)); - acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3_0), - vget_low_s16(input_val_0)); - acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0_0), - vget_high_s16(input_val_0)); - acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1_0), - vget_high_s16(input_val_0)); - acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2_0), - vget_high_s16(input_val_0)); - acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3_0), - vget_high_s16(input_val_0)); - acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0_1), - vget_low_s16(input_val_1)); - acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1_1), - vget_low_s16(input_val_1)); - acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2_1), - vget_low_s16(input_val_1)); - acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3_1), - vget_low_s16(input_val_1)); - acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0_1), - vget_high_s16(input_val_1)); - acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1_1), - vget_high_s16(input_val_1)); - acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2_1), - vget_high_s16(input_val_1)); - acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3_1), - vget_high_s16(input_val_1)); - } - // Handle 8 levels of depth at a time. - for (; in < input_size; in += 8) { - const uint8x8_t input_val_u8 = vld1_u8(input_data + in); - const uint8* weights_ptr = weights_data + in + out * input_size; - uint8x8_t weights_val_u8_0 = vld1_u8(weights_ptr + 0 * input_size); - uint8x8_t weights_val_u8_1 = vld1_u8(weights_ptr + 1 * input_size); - uint8x8_t weights_val_u8_2 = vld1_u8(weights_ptr + 2 * input_size); - uint8x8_t weights_val_u8_3 = vld1_u8(weights_ptr + 3 * input_size); - int16x8_t input_val; - input_val = vreinterpretq_s16_u16(vmovl_u8(input_val_u8)); - input_val = vaddq_s16(input_val, input_offset_vec); - int16x8_t weights_val_0, weights_val_1, weights_val_2, weights_val_3; - weights_val_0 = - vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_0)), - weights_offset_vec); - weights_val_1 = - vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_1)), - weights_offset_vec); - weights_val_2 = - vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_2)), - weights_offset_vec); - weights_val_3 = - vaddq_s16(vreinterpretq_s16_u16(vmovl_u8(weights_val_u8_3)), - weights_offset_vec); - acc_0 = vmlal_s16(acc_0, vget_low_s16(weights_val_0), - vget_low_s16(input_val)); - acc_1 = vmlal_s16(acc_1, vget_low_s16(weights_val_1), - vget_low_s16(input_val)); - acc_2 = vmlal_s16(acc_2, vget_low_s16(weights_val_2), - vget_low_s16(input_val)); - acc_3 = vmlal_s16(acc_3, vget_low_s16(weights_val_3), - vget_low_s16(input_val)); - acc_0 = vmlal_s16(acc_0, vget_high_s16(weights_val_0), - vget_high_s16(input_val)); - acc_1 = vmlal_s16(acc_1, vget_high_s16(weights_val_1), - vget_high_s16(input_val)); - acc_2 = vmlal_s16(acc_2, vget_high_s16(weights_val_2), - vget_high_s16(input_val)); - acc_3 = vmlal_s16(acc_3, vget_high_s16(weights_val_3), - vget_high_s16(input_val)); - } - // Horizontally reduce accumulators - int32x2_t pairwise_reduced_acc_0, pairwise_reduced_acc_1, - pairwise_reduced_acc_2, pairwise_reduced_acc_3; - pairwise_reduced_acc_0 = - vpadd_s32(vget_low_s32(acc_0), vget_high_s32(acc_0)); - pairwise_reduced_acc_1 = - vpadd_s32(vget_low_s32(acc_1), vget_high_s32(acc_1)); - pairwise_reduced_acc_2 = - vpadd_s32(vget_low_s32(acc_2), vget_high_s32(acc_2)); - pairwise_reduced_acc_3 = - vpadd_s32(vget_low_s32(acc_3), vget_high_s32(acc_3)); - const int32x2_t reduced_lo = - vpadd_s32(pairwise_reduced_acc_0, pairwise_reduced_acc_1); - const int32x2_t reduced_hi = - vpadd_s32(pairwise_reduced_acc_2, pairwise_reduced_acc_3); - int32x4_t reduced = vcombine_s32(reduced_lo, reduced_hi); - // Add bias values. - int32x4_t bias_vec = vld1q_s32(bias_ptr); - bias_ptr += 4; - reduced = vaddq_s32(reduced, bias_vec); - int left_shift = accum_shift > 0 ? accum_shift : 0; - int right_shift = accum_shift > 0 ? 0 : -accum_shift; - reduced = vshlq_s32(reduced, vdupq_n_s32(left_shift)); - // Multiply by the fixed-point multiplier. - reduced = vqrdmulhq_n_s32(reduced, accum_multiplier); - // Rounding-shift-right. - using gemmlowp::RoundingDivideByPOT; - reduced = RoundingDivideByPOT(reduced, right_shift); - // Narrow values down to 16 bit signed. - const int16x4_t res16 = vqmovn_s32(reduced); - vst1_s16(output_ptr, res16); - output_ptr += 4; - } -} -#endif - // Quantized LSTM cell. Currently just a copy of the reference impl in // reference_ops.h. See the big function comment there, not replicating it // here. -- GitLab From 4c909d283d7efab3e0dde68eb27d31d68407e207 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 13:11:18 -0700 Subject: [PATCH 224/906] Add header guard to lstm_utils. PiperOrigin-RevId: 190512302 --- .../contrib/lite/toco/graph_transformations/lstm_utils.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/lstm_utils.h b/tensorflow/contrib/lite/toco/graph_transformations/lstm_utils.h index 881c2d4dc8..4a9974ed4e 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/lstm_utils.h +++ b/tensorflow/contrib/lite/toco/graph_transformations/lstm_utils.h @@ -12,6 +12,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#ifndef TENSORFLOW_CONTRIB_LITE_TOCO_GRAPH_TRANSFORMATIONS_LSTM_UTILS_H_ +#define TENSORFLOW_CONTRIB_LITE_TOCO_GRAPH_TRANSFORMATIONS_LSTM_UTILS_H_ + #include #include #include @@ -100,3 +103,5 @@ bool GetMatchingRnnArray(Model* model, const string& back_edge_source_array, string* rnn_array); } // namespace toco + +#endif // TENSORFLOW_CONTRIB_LITE_TOCO_GRAPH_TRANSFORMATIONS_LSTM_UTILS_H_ -- GitLab From 3f708534f7fa5d548c2ccd0a77a229a815868e8f Mon Sep 17 00:00:00 2001 From: Anna R Date: Mon, 26 Mar 2018 13:15:53 -0700 Subject: [PATCH 225/906] Internal change. PiperOrigin-RevId: 190512928 --- tensorflow/contrib/distributions/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/distributions/BUILD b/tensorflow/contrib/distributions/BUILD index e9c827a618..1c381cc354 100644 --- a/tensorflow/contrib/distributions/BUILD +++ b/tensorflow/contrib/distributions/BUILD @@ -490,6 +490,7 @@ cuda_py_test( "manual", "noasan", "noguitar", + "optonly", ], ) -- GitLab From 5427f60f69c3f22bc5e40b3c51a484dd3af504fb Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Mon, 26 Mar 2018 13:25:58 -0700 Subject: [PATCH 226/906] Add additional protobuf imports. PiperOrigin-RevId: 190514839 --- tensorflow/core/platform/default/protobuf.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/platform/default/protobuf.h b/tensorflow/core/platform/default/protobuf.h index 03d8b6c238..c732c76ff7 100644 --- a/tensorflow/core/platform/default/protobuf.h +++ b/tensorflow/core/platform/default/protobuf.h @@ -22,6 +22,7 @@ limitations under the License. #include "google/protobuf/arena.h" #include "google/protobuf/compiler/importer.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/dynamic_message.h" #include "google/protobuf/io/coded_stream.h" #include "google/protobuf/io/zero_copy_stream.h" #include "google/protobuf/io/zero_copy_stream_impl_lite.h" -- GitLab From 54cc8b35f1062f385f0e97c397e1ae96c91c9f62 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 13:30:17 -0700 Subject: [PATCH 227/906] Global rename of py2tf to autograph PiperOrigin-RevId: 190515509 --- tensorflow/BUILD | 12 ++-- tensorflow/contrib/BUILD | 2 +- tensorflow/contrib/{py2tf => autograph}/BUILD | 8 +-- .../contrib/{py2tf => autograph}/README.md | 2 +- .../contrib/{py2tf => autograph}/__init__.py | 20 +++--- .../{py2tf => autograph}/converters/BUILD | 15 +++-- .../converters/__init__.py | 2 +- .../converters/asserts.py | 4 +- .../converters/asserts_test.py | 4 +- .../converters/break_statements.py | 8 +-- .../converters/break_statements_test.py | 4 +- .../converters/builtin_functions.py | 8 +-- .../converters/builtin_functions_test.py | 4 +- .../converters/call_trees.py | 21 +++--- .../converters/call_trees_test.py | 4 +- .../converters/continue_statements.py | 8 +-- .../converters/continue_statements_test.py | 4 +- .../converters/control_flow.py | 16 ++--- .../converters/control_flow_test.py | 4 +- .../converters/converter_test_base.py | 22 +++---- .../converters/decorators.py | 4 +- .../converters/decorators_test.py | 6 +- .../converters/for_loops.py | 20 +++--- .../converters/for_loops_test.py | 4 +- .../{py2tf => autograph}/converters/ifexp.py | 6 +- .../converters/ifexp_test.py | 18 ++--- .../converters/list_comprehension.py | 6 +- .../converters/list_comprehension_test.py | 4 +- .../{py2tf => autograph}/converters/lists.py | 8 +-- .../converters/lists_test.py | 6 +- .../converters/logical_expressions.py | 12 ++-- .../converters/logical_expressions_test.py | 4 +- .../converters/name_scopes.py | 4 +- .../converters/name_scopes_test.py | 4 +- .../converters/side_effect_guards.py | 18 ++--- .../converters/side_effect_guards_test.py | 4 +- .../converters/single_return.py | 12 ++-- .../converters/single_return_test.py | 4 +- .../contrib/{py2tf => autograph}/impl/BUILD | 10 +-- .../contrib/{py2tf => autograph}/impl/api.py | 16 ++--- .../{py2tf => autograph}/impl/api_test.py | 18 +++-- .../{py2tf => autograph}/impl/config.py | 16 ++--- .../{py2tf => autograph}/impl/conversion.py | 66 +++++++++---------- .../impl/conversion_test.py | 4 +- .../{py2tf => autograph}/impl/naming.py | 2 +- .../{py2tf => autograph}/impl/naming_test.py | 2 +- .../contrib/{py2tf => autograph}/pyct/BUILD | 0 .../{py2tf => autograph}/pyct/__init__.py | 0 .../contrib/{py2tf => autograph}/pyct/anno.py | 0 .../{py2tf => autograph}/pyct/anno_test.py | 2 +- .../{py2tf => autograph}/pyct/ast_util.py | 2 +- .../pyct/ast_util_test.py | 8 +-- .../{py2tf => autograph}/pyct/compiler.py | 0 .../pyct/compiler_test.py | 4 +- .../{py2tf => autograph}/pyct/context.py | 0 .../pyct/inspect_utils.py | 0 .../pyct/inspect_utils_test.py | 2 +- .../{py2tf => autograph}/pyct/parser.py | 0 .../{py2tf => autograph}/pyct/parser_test.py | 2 +- .../pyct/pretty_printer.py | 0 .../pyct/pretty_printer_test.py | 2 +- .../{py2tf => autograph}/pyct/qual_names.py | 2 +- .../pyct/qual_names_test.py | 10 +-- .../pyct/static_analysis/BUILD | 10 +-- .../pyct/static_analysis/__init__.py | 0 .../pyct/static_analysis/activity.py | 8 +-- .../pyct/static_analysis/activity_test.py | 14 ++-- .../pyct/static_analysis/annos.py | 0 .../pyct/static_analysis/live_values.py | 6 +- .../pyct/static_analysis/live_values_test.py | 14 ++-- .../pyct/static_analysis/type_info.py | 4 +- .../pyct/static_analysis/type_info_test.py | 16 ++--- .../{py2tf => autograph}/pyct/templates.py | 6 +- .../pyct/templates_test.py | 6 +- .../{py2tf => autograph}/pyct/transformer.py | 12 ++-- .../contrib/{py2tf => autograph}/utils/BUILD | 0 .../contrib/autograph/utils/__init__.py | 36 ++++++++++ .../{py2tf => autograph}/utils/builtins.py | 4 +- .../utils/builtins_test.py | 2 +- .../utils/context_managers.py | 0 .../utils/context_managers_test.py | 2 +- .../{py2tf => autograph}/utils/misc.py | 0 .../{py2tf => autograph}/utils/misc_test.py | 2 +- .../utils/multiple_dispatch.py | 4 +- .../utils/multiple_dispatch_test.py | 2 +- .../{py2tf => autograph}/utils/py_func.py | 5 +- .../utils/py_func_test.py | 2 +- .../{py2tf => autograph}/utils/tensor_list.py | 0 .../utils/tensor_list_test.py | 4 +- .../{py2tf => autograph}/utils/testing.py | 0 .../{py2tf => autograph}/utils/type_check.py | 2 +- .../utils/type_check_test.py | 2 +- .../{py2tf => autograph}/utils/type_hints.py | 0 tensorflow/contrib/py2tf/utils/__init__.py | 36 ---------- tensorflow/tools/pip_package/BUILD | 12 ++-- 95 files changed, 346 insertions(+), 349 deletions(-) rename tensorflow/contrib/{py2tf => autograph}/BUILD (75%) rename tensorflow/contrib/{py2tf => autograph}/README.md (87%) rename tensorflow/contrib/{py2tf => autograph}/__init__.py (64%) rename tensorflow/contrib/{py2tf => autograph}/converters/BUILD (92%) rename tensorflow/contrib/{py2tf => autograph}/converters/__init__.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/asserts.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/asserts_test.py (90%) rename tensorflow/contrib/{py2tf => autograph}/converters/break_statements.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/break_statements_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/builtin_functions.py (92%) rename tensorflow/contrib/{py2tf => autograph}/converters/builtin_functions_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/converters/call_trees.py (94%) rename tensorflow/contrib/{py2tf => autograph}/converters/call_trees_test.py (97%) rename tensorflow/contrib/{py2tf => autograph}/converters/continue_statements.py (94%) rename tensorflow/contrib/{py2tf => autograph}/converters/continue_statements_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/control_flow.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/control_flow_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/converter_test_base.py (85%) rename tensorflow/contrib/{py2tf => autograph}/converters/decorators.py (96%) rename tensorflow/contrib/{py2tf => autograph}/converters/decorators_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/for_loops.py (80%) rename tensorflow/contrib/{py2tf => autograph}/converters/for_loops_test.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/ifexp.py (88%) rename tensorflow/contrib/{py2tf => autograph}/converters/ifexp_test.py (86%) rename tensorflow/contrib/{py2tf => autograph}/converters/list_comprehension.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/list_comprehension_test.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/lists.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/lists_test.py (90%) rename tensorflow/contrib/{py2tf => autograph}/converters/logical_expressions.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/logical_expressions_test.py (92%) rename tensorflow/contrib/{py2tf => autograph}/converters/name_scopes.py (93%) rename tensorflow/contrib/{py2tf => autograph}/converters/name_scopes_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/converters/side_effect_guards.py (91%) rename tensorflow/contrib/{py2tf => autograph}/converters/side_effect_guards_test.py (97%) rename tensorflow/contrib/{py2tf => autograph}/converters/single_return.py (96%) rename tensorflow/contrib/{py2tf => autograph}/converters/single_return_test.py (97%) rename tensorflow/contrib/{py2tf => autograph}/impl/BUILD (82%) rename tensorflow/contrib/{py2tf => autograph}/impl/api.py (95%) rename tensorflow/contrib/{py2tf => autograph}/impl/api_test.py (92%) rename tensorflow/contrib/{py2tf => autograph}/impl/config.py (79%) rename tensorflow/contrib/{py2tf => autograph}/impl/conversion.py (84%) rename tensorflow/contrib/{py2tf => autograph}/impl/conversion_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/impl/naming.py (98%) rename tensorflow/contrib/{py2tf => autograph}/impl/naming_test.py (98%) rename tensorflow/contrib/{py2tf => autograph}/pyct/BUILD (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/__init__.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/anno.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/anno_test.py (97%) rename tensorflow/contrib/{py2tf => autograph}/pyct/ast_util.py (98%) rename tensorflow/contrib/{py2tf => autograph}/pyct/ast_util_test.py (93%) rename tensorflow/contrib/{py2tf => autograph}/pyct/compiler.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/compiler_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/context.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/inspect_utils.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/inspect_utils_test.py (98%) rename tensorflow/contrib/{py2tf => autograph}/pyct/parser.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/parser_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/pretty_printer.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/pretty_printer_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/qual_names.py (99%) rename tensorflow/contrib/{py2tf => autograph}/pyct/qual_names_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/BUILD (83%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/__init__.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/activity.py (97%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/activity_test.py (95%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/annos.py (100%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/live_values.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/live_values_test.py (89%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/type_info.py (98%) rename tensorflow/contrib/{py2tf => autograph}/pyct/static_analysis/type_info_test.py (93%) rename tensorflow/contrib/{py2tf => autograph}/pyct/templates.py (98%) rename tensorflow/contrib/{py2tf => autograph}/pyct/templates_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/pyct/transformer.py (89%) rename tensorflow/contrib/{py2tf => autograph}/utils/BUILD (100%) create mode 100644 tensorflow/contrib/autograph/utils/__init__.py rename tensorflow/contrib/{py2tf => autograph}/utils/builtins.py (98%) rename tensorflow/contrib/{py2tf => autograph}/utils/builtins_test.py (98%) rename tensorflow/contrib/{py2tf => autograph}/utils/context_managers.py (100%) rename tensorflow/contrib/{py2tf => autograph}/utils/context_managers_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/utils/misc.py (100%) rename tensorflow/contrib/{py2tf => autograph}/utils/misc_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/utils/multiple_dispatch.py (95%) rename tensorflow/contrib/{py2tf => autograph}/utils/multiple_dispatch_test.py (98%) rename tensorflow/contrib/{py2tf => autograph}/utils/py_func.py (97%) rename tensorflow/contrib/{py2tf => autograph}/utils/py_func_test.py (98%) rename tensorflow/contrib/{py2tf => autograph}/utils/tensor_list.py (100%) rename tensorflow/contrib/{py2tf => autograph}/utils/tensor_list_test.py (97%) rename tensorflow/contrib/{py2tf => autograph}/utils/testing.py (100%) rename tensorflow/contrib/{py2tf => autograph}/utils/type_check.py (95%) rename tensorflow/contrib/{py2tf => autograph}/utils/type_check_test.py (96%) rename tensorflow/contrib/{py2tf => autograph}/utils/type_hints.py (100%) delete mode 100644 tensorflow/contrib/py2tf/utils/__init__.py diff --git a/tensorflow/BUILD b/tensorflow/BUILD index c75bf8abab..b073adfee9 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -448,6 +448,12 @@ filegroup( "//tensorflow/contrib:all_files", "//tensorflow/contrib/all_reduce:all_files", "//tensorflow/contrib/android:all_files", + "//tensorflow/contrib/autograph:all_files", + "//tensorflow/contrib/autograph/converters:all_files", + "//tensorflow/contrib/autograph/impl:all_files", + "//tensorflow/contrib/autograph/pyct:all_files", + "//tensorflow/contrib/autograph/pyct/static_analysis:all_files", + "//tensorflow/contrib/autograph/utils:all_files", "//tensorflow/contrib/batching:all_files", "//tensorflow/contrib/bayesflow:all_files", "//tensorflow/contrib/boosted_trees:all_files", @@ -541,12 +547,6 @@ filegroup( "//tensorflow/contrib/opt:all_files", "//tensorflow/contrib/periodic_resample:all_files", "//tensorflow/contrib/predictor:all_files", - "//tensorflow/contrib/py2tf:all_files", - "//tensorflow/contrib/py2tf/converters:all_files", - "//tensorflow/contrib/py2tf/impl:all_files", - "//tensorflow/contrib/py2tf/pyct:all_files", - "//tensorflow/contrib/py2tf/pyct/static_analysis:all_files", - "//tensorflow/contrib/py2tf/utils:all_files", "//tensorflow/contrib/quantize:all_files", "//tensorflow/contrib/receptive_field:all_files", "//tensorflow/contrib/reduce_slice_ops:all_files", diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index 2d7bbc016f..bdbd738906 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -79,7 +79,7 @@ py_library( "//tensorflow/contrib/predictor", "//tensorflow/contrib/quantization:quantization_py", "//tensorflow/contrib/quantize:quantize_graph", - "//tensorflow/contrib/py2tf", + "//tensorflow/contrib/autograph", "//tensorflow/contrib/receptive_field:receptive_field_py", "//tensorflow/contrib/reduce_slice_ops:reduce_slice_ops_py", "//tensorflow/contrib/remote_fused_graph/pylib:remote_fused_graph_ops_py", diff --git a/tensorflow/contrib/py2tf/BUILD b/tensorflow/contrib/autograph/BUILD similarity index 75% rename from tensorflow/contrib/py2tf/BUILD rename to tensorflow/contrib/autograph/BUILD index d91220f6dd..30dd846893 100644 --- a/tensorflow/contrib/py2tf/BUILD +++ b/tensorflow/contrib/autograph/BUILD @@ -15,16 +15,16 @@ filegroup( ) py_library( - name = "py2tf", + name = "autograph", srcs = [ "__init__.py", ], srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ - "//tensorflow/contrib/py2tf/impl", - "//tensorflow/contrib/py2tf/pyct", - "//tensorflow/contrib/py2tf/utils", + "//tensorflow/contrib/autograph/impl", + "//tensorflow/contrib/autograph/pyct", + "//tensorflow/contrib/autograph/utils", "@gast_archive//:gast", "@six_archive//:six", ], diff --git a/tensorflow/contrib/py2tf/README.md b/tensorflow/contrib/autograph/README.md similarity index 87% rename from tensorflow/contrib/py2tf/README.md rename to tensorflow/contrib/autograph/README.md index cd50675ad5..7e84f237dc 100644 --- a/tensorflow/contrib/py2tf/README.md +++ b/tensorflow/contrib/autograph/README.md @@ -1,4 +1,4 @@ -# Py2TF +# Autograph A compiler for generating TensorFlow numeric and control flow ops from Python code. diff --git a/tensorflow/contrib/py2tf/__init__.py b/tensorflow/contrib/autograph/__init__.py similarity index 64% rename from tensorflow/contrib/py2tf/__init__.py rename to tensorflow/contrib/autograph/__init__.py index a4b62a0976..a39f44b21a 100644 --- a/tensorflow/contrib/py2tf/__init__.py +++ b/tensorflow/contrib/autograph/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Py2TF compiles Python code into equivalent TensorFlow code. +"""Autograph compiles Python code into equivalent TensorFlow code. Equivalent here means that they have the same effect when executed. """ @@ -21,19 +21,19 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.impl.api import convert -from tensorflow.contrib.py2tf.impl.api import converted_call -from tensorflow.contrib.py2tf.impl.api import do_not_convert -from tensorflow.contrib.py2tf.impl.api import RunMode -from tensorflow.contrib.py2tf.impl.api import to_code -from tensorflow.contrib.py2tf.impl.api import to_graph -from tensorflow.contrib.py2tf.pyct.transformer import PyFlowParseError +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.impl.api import convert +from tensorflow.contrib.autograph.impl.api import converted_call +from tensorflow.contrib.autograph.impl.api import do_not_convert +from tensorflow.contrib.autograph.impl.api import RunMode +from tensorflow.contrib.autograph.impl.api import to_code +from tensorflow.contrib.autograph.impl.api import to_graph +from tensorflow.contrib.autograph.pyct.transformer import AutographParseError from tensorflow.python.util.all_util import remove_undocumented _allowed_symbols = [ 'utils', 'convert', 'converted_call', 'do_not_convert', 'RunMode', - 'to_code', 'to_graph', 'PyFlowParseError' + 'to_code', 'to_graph', 'AutographParseError' ] remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/py2tf/converters/BUILD b/tensorflow/contrib/autograph/converters/BUILD similarity index 92% rename from tensorflow/contrib/py2tf/converters/BUILD rename to tensorflow/contrib/autograph/converters/BUILD index f624c42686..608bd82722 100644 --- a/tensorflow/contrib/py2tf/converters/BUILD +++ b/tensorflow/contrib/autograph/converters/BUILD @@ -49,9 +49,9 @@ py_library( visibility = ["//tensorflow:__subpackages__"], deps = [ ":converters", - "//tensorflow/contrib/py2tf/pyct", - "//tensorflow/contrib/py2tf/pyct/static_analysis", - "//tensorflow/contrib/py2tf/utils", + "//tensorflow/contrib/autograph/pyct", + "//tensorflow/contrib/autograph/pyct/static_analysis", + "//tensorflow/contrib/autograph/utils", "@gast_archive//:gast", "@six_archive//:six", ], @@ -89,11 +89,12 @@ py_test( py_test( name = "call_trees_test", + size = "large", srcs = ["call_trees_test.py"], srcs_version = "PY2AND3", deps = [ ":test_lib", - "//tensorflow/contrib/py2tf/impl", + "//tensorflow/contrib/autograph/impl", "//tensorflow/python:client_testlib", ], ) @@ -143,7 +144,7 @@ py_test( srcs = ["name_scopes_test.py"], deps = [ ":test_lib", - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "//tensorflow/python:client_testlib", ], ) @@ -199,7 +200,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":test_lib", - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "//tensorflow/python:client_testlib", ], ) @@ -210,7 +211,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":test_lib", - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "//tensorflow/python:client_testlib", ], ) diff --git a/tensorflow/contrib/py2tf/converters/__init__.py b/tensorflow/contrib/autograph/converters/__init__.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/__init__.py rename to tensorflow/contrib/autograph/converters/__init__.py index ca10896ee5..e4e8eda42f 100644 --- a/tensorflow/contrib/py2tf/converters/__init__.py +++ b/tensorflow/contrib/autograph/converters/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Code converters used by Py2TF.""" +"""Code converters used by Autograph.""" from __future__ import absolute_import from __future__ import division diff --git a/tensorflow/contrib/py2tf/converters/asserts.py b/tensorflow/contrib/autograph/converters/asserts.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/asserts.py rename to tensorflow/contrib/autograph/converters/asserts.py index 5b9b8e772b..f011a97ade 100644 --- a/tensorflow/contrib/py2tf/converters/asserts.py +++ b/tensorflow/contrib/autograph/converters/asserts.py @@ -20,8 +20,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer class AssertsTransformer(transformer.Base): diff --git a/tensorflow/contrib/py2tf/converters/asserts_test.py b/tensorflow/contrib/autograph/converters/asserts_test.py similarity index 90% rename from tensorflow/contrib/py2tf/converters/asserts_test.py rename to tensorflow/contrib/autograph/converters/asserts_test.py index 6611f2777a..cc913febe8 100644 --- a/tensorflow/contrib/py2tf/converters/asserts_test.py +++ b/tensorflow/contrib/autograph/converters/asserts_test.py @@ -20,8 +20,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.converters import asserts -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import asserts +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/break_statements.py b/tensorflow/contrib/autograph/converters/break_statements.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/break_statements.py rename to tensorflow/contrib/autograph/converters/break_statements.py index bfb709c5e3..721bc0ccd0 100644 --- a/tensorflow/contrib/py2tf/converters/break_statements.py +++ b/tensorflow/contrib/autograph/converters/break_statements.py @@ -20,10 +20,10 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class BreakCanonicalizationTransformer(transformer.Base): diff --git a/tensorflow/contrib/py2tf/converters/break_statements_test.py b/tensorflow/contrib/autograph/converters/break_statements_test.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/break_statements_test.py rename to tensorflow/contrib/autograph/converters/break_statements_test.py index 095fcdff07..dd4914a022 100644 --- a/tensorflow/contrib/py2tf/converters/break_statements_test.py +++ b/tensorflow/contrib/autograph/converters/break_statements_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import break_statements -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import break_statements +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/builtin_functions.py b/tensorflow/contrib/autograph/converters/builtin_functions.py similarity index 92% rename from tensorflow/contrib/py2tf/converters/builtin_functions.py rename to tensorflow/contrib/autograph/converters/builtin_functions.py index f1129ef153..0349ce29ce 100644 --- a/tensorflow/contrib/py2tf/converters/builtin_functions.py +++ b/tensorflow/contrib/autograph/converters/builtin_functions.py @@ -20,8 +20,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer class BuiltinFunctionTransformer(transformer.Base): @@ -38,13 +38,13 @@ class BuiltinFunctionTransformer(transformer.Base): def _convert_builtin(self, node): template = """ - py2tf_utils.dynamic_builtin(func, args) + autograph_utils.dynamic_builtin(func, args) """ return templates.replace(template, func=node.func, args=node.args)[0].value def _convert_print(self, node): template = """ - py2tf_utils.dynamic_print(args) + autograph_utils.dynamic_print(args) """ return templates.replace(template, args=node.args)[0].value diff --git a/tensorflow/contrib/py2tf/converters/builtin_functions_test.py b/tensorflow/contrib/autograph/converters/builtin_functions_test.py similarity index 96% rename from tensorflow/contrib/py2tf/converters/builtin_functions_test.py rename to tensorflow/contrib/autograph/converters/builtin_functions_test.py index eb60a1d8ae..ac7e756c47 100644 --- a/tensorflow/contrib/py2tf/converters/builtin_functions_test.py +++ b/tensorflow/contrib/autograph/converters/builtin_functions_test.py @@ -22,8 +22,8 @@ import sys import six -from tensorflow.contrib.py2tf.converters import builtin_functions -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import builtin_functions +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.framework import constant_op from tensorflow.python.ops import array_ops from tensorflow.python.ops import logging_ops diff --git a/tensorflow/contrib/py2tf/converters/call_trees.py b/tensorflow/contrib/autograph/converters/call_trees.py similarity index 94% rename from tensorflow/contrib/py2tf/converters/call_trees.py rename to tensorflow/contrib/autograph/converters/call_trees.py index f498b814bf..61f6bfd7e7 100644 --- a/tensorflow/contrib/py2tf/converters/call_trees.py +++ b/tensorflow/contrib/autograph/converters/call_trees.py @@ -27,12 +27,12 @@ import types import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import inspect_utils -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import inspect_utils +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer from tensorflow.python.util import tf_inspect @@ -199,7 +199,7 @@ class CallTreeTransformer(transformer.Base): def _wrap_to_py_func_no_return(self, node): # TODO(mdan): Properly handle varargs, etc. template = """ - py2tf_utils.wrap_py_func(func, None, (args,), kwargs, True) + autograph_utils.wrap_py_func(func, None, (args,), kwargs, True) """ return templates.replace( template, @@ -210,7 +210,7 @@ class CallTreeTransformer(transformer.Base): def _wrap_to_py_func_single_return(self, node, dtype): # TODO(mdan): Properly handle varargs, etc. template = """ - py2tf_utils.wrap_py_func(func, dtype, (args,), kwargs, False) + autograph_utils.wrap_py_func(func, dtype, (args,), kwargs, False) """ return templates.replace_as_expression( template, @@ -238,10 +238,9 @@ class CallTreeTransformer(transformer.Base): # Before we could convert all the time though, we'd need a reasonable # caching mechanism. template = """ - py2tf_api.converted_call(func, True, False, {}, args) + autograph_api.converted_call(func, True, False, {}, args) """ - call_expr = templates.replace( - template, func=node.func, args=node.args) + call_expr = templates.replace(template, func=node.func, args=node.args) new_call = call_expr[0].value # TODO(mdan): Improve the template mechanism to better support this. new_call.keywords = node.keywords diff --git a/tensorflow/contrib/py2tf/converters/call_trees_test.py b/tensorflow/contrib/autograph/converters/call_trees_test.py similarity index 97% rename from tensorflow/contrib/py2tf/converters/call_trees_test.py rename to tensorflow/contrib/autograph/converters/call_trees_test.py index 1106432da6..c666dcb73b 100644 --- a/tensorflow/contrib/py2tf/converters/call_trees_test.py +++ b/tensorflow/contrib/autograph/converters/call_trees_test.py @@ -20,8 +20,8 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.py2tf.converters import call_trees -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import call_trees +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops diff --git a/tensorflow/contrib/py2tf/converters/continue_statements.py b/tensorflow/contrib/autograph/converters/continue_statements.py similarity index 94% rename from tensorflow/contrib/py2tf/converters/continue_statements.py rename to tensorflow/contrib/autograph/converters/continue_statements.py index 4069a678b1..4299a8a9d5 100644 --- a/tensorflow/contrib/py2tf/converters/continue_statements.py +++ b/tensorflow/contrib/autograph/converters/continue_statements.py @@ -18,10 +18,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class ContinueCanonicalizationTransformer(transformer.Base): diff --git a/tensorflow/contrib/py2tf/converters/continue_statements_test.py b/tensorflow/contrib/autograph/converters/continue_statements_test.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/continue_statements_test.py rename to tensorflow/contrib/autograph/converters/continue_statements_test.py index a598dcd1ae..bcbb316d74 100644 --- a/tensorflow/contrib/py2tf/converters/continue_statements_test.py +++ b/tensorflow/contrib/autograph/converters/continue_statements_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import continue_statements -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import continue_statements +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/control_flow.py b/tensorflow/contrib/autograph/converters/control_flow.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/control_flow.py rename to tensorflow/contrib/autograph/converters/control_flow.py index 762c26f0c7..49d932026f 100644 --- a/tensorflow/contrib/py2tf/converters/control_flow.py +++ b/tensorflow/contrib/autograph/converters/control_flow.py @@ -20,11 +20,11 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class SymbolNamer(object): @@ -82,7 +82,7 @@ class ControlFlowTransformer(transformer.Base): def _create_cond_expr(self, results, test, body_name, orelse_name): if results is not None: template = """ - results = py2tf_utils.run_cond(test, body_name, orelse_name) + results = autograph_utils.run_cond(test, body_name, orelse_name) """ return templates.replace( template, @@ -92,7 +92,7 @@ class ControlFlowTransformer(transformer.Base): orelse_name=orelse_name) else: template = """ - py2tf_utils.run_cond(test, body_name, orelse_name) + autograph_utils.run_cond(test, body_name, orelse_name) """ return templates.replace( template, test=test, body_name=body_name, orelse_name=orelse_name) @@ -204,7 +204,7 @@ class ControlFlowTransformer(transformer.Base): def body_name(state_ssf): body return state_ssf, - state_ast_tuple = py2tf_utils.run_while(test_name, body_name, [state]) + state_ast_tuple = autograph_utils.run_while(test_name, body_name, [state]) """ node = templates.replace( template, diff --git a/tensorflow/contrib/py2tf/converters/control_flow_test.py b/tensorflow/contrib/autograph/converters/control_flow_test.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/control_flow_test.py rename to tensorflow/contrib/autograph/converters/control_flow_test.py index b785b284a7..86fed51f27 100644 --- a/tensorflow/contrib/py2tf/converters/control_flow_test.py +++ b/tensorflow/contrib/autograph/converters/control_flow_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import control_flow -from tensorflow.contrib.py2tf.converters import converter_test_base +from tensorflow.contrib.autograph.converters import control_flow +from tensorflow.contrib.autograph.converters import converter_test_base from tensorflow.python.framework import constant_op from tensorflow.python.ops import control_flow_ops from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/converter_test_base.py b/tensorflow/contrib/autograph/converters/converter_test_base.py similarity index 85% rename from tensorflow/contrib/py2tf/converters/converter_test_base.py rename to tensorflow/contrib/autograph/converters/converter_test_base.py index 8c08c5492a..3ea2cfd668 100644 --- a/tensorflow/contrib/py2tf/converters/converter_test_base.py +++ b/tensorflow/contrib/autograph/converters/converter_test_base.py @@ -21,15 +21,15 @@ from __future__ import print_function import contextlib import imp -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import context -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import pretty_printer -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.static_analysis import activity -from tensorflow.contrib.py2tf.pyct.static_analysis import live_values -from tensorflow.contrib.py2tf.pyct.static_analysis import type_info +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import context +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import pretty_printer +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.static_analysis import activity +from tensorflow.contrib.autograph.pyct.static_analysis import live_values +from tensorflow.contrib.autograph.pyct.static_analysis import type_info from tensorflow.python.platform import test @@ -75,8 +75,8 @@ class TestCase(test.TestCase): try: result, source = compiler.ast_to_object(node) result.tf = self.make_fake_mod('fake_tf', *symbols) - result.py2tf_utils = utils - result.py2tf_api = self.make_fake_mod('fake_api', converted_call) + result.autograph_utils = utils + result.autograph_api = self.make_fake_mod('fake_api', converted_call) yield result except Exception: # pylint:disable=broad-except if source is None: diff --git a/tensorflow/contrib/py2tf/converters/decorators.py b/tensorflow/contrib/autograph/converters/decorators.py similarity index 96% rename from tensorflow/contrib/py2tf/converters/decorators.py rename to tensorflow/contrib/autograph/converters/decorators.py index 68bf241ef3..92445f3174 100644 --- a/tensorflow/contrib/py2tf/converters/decorators.py +++ b/tensorflow/contrib/autograph/converters/decorators.py @@ -24,8 +24,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import pretty_printer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import pretty_printer class DecoratorsTransformer(gast.NodeTransformer): diff --git a/tensorflow/contrib/py2tf/converters/decorators_test.py b/tensorflow/contrib/autograph/converters/decorators_test.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/decorators_test.py rename to tensorflow/contrib/autograph/converters/decorators_test.py index c75e546174..e67ab1cd6a 100644 --- a/tensorflow/contrib/py2tf/converters/decorators_test.py +++ b/tensorflow/contrib/autograph/converters/decorators_test.py @@ -20,9 +20,9 @@ from __future__ import print_function from functools import wraps -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import decorators -from tensorflow.contrib.py2tf.pyct import compiler +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import decorators +from tensorflow.contrib.autograph.pyct import compiler from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/for_loops.py b/tensorflow/contrib/autograph/converters/for_loops.py similarity index 80% rename from tensorflow/contrib/py2tf/converters/for_loops.py rename to tensorflow/contrib/autograph/converters/for_loops.py index 8d28b149a8..4999c47bdc 100644 --- a/tensorflow/contrib/py2tf/converters/for_loops.py +++ b/tensorflow/contrib/autograph/converters/for_loops.py @@ -22,10 +22,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class ForLoopCanonicalizationTransformer(transformer.Base): @@ -45,12 +45,12 @@ class ForLoopCanonicalizationTransformer(transformer.Base): if anno.hasanno(node, 'extra_cond'): template = """ i = 0 - smart_loop_iter = py2tf_utils.dynamic_dataset(loop_iter) - cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + smart_loop_iter = autograph_utils.dynamic_dataset(loop_iter) + cont, target = autograph_utils.dynamic_for_cond(i, smart_loop_iter) while cont and extra_cond: body i += 1 - cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + cont, target = autograph_utils.dynamic_for_cond(i, smart_loop_iter) """ return templates.replace( template, @@ -64,12 +64,12 @@ class ForLoopCanonicalizationTransformer(transformer.Base): else: template = """ i = 0 - smart_loop_iter = py2tf_utils.dynamic_dataset(loop_iter) - cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + smart_loop_iter = autograph_utils.dynamic_dataset(loop_iter) + cont, target = autograph_utils.dynamic_for_cond(i, smart_loop_iter) while cont: body i += 1 - cont, target = py2tf_utils.dynamic_for_cond(i, smart_loop_iter) + cont, target = autograph_utils.dynamic_for_cond(i, smart_loop_iter) """ repl = templates.replace( template, diff --git a/tensorflow/contrib/py2tf/converters/for_loops_test.py b/tensorflow/contrib/autograph/converters/for_loops_test.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/for_loops_test.py rename to tensorflow/contrib/autograph/converters/for_loops_test.py index b6e3e8c8d8..943f52de55 100644 --- a/tensorflow/contrib/py2tf/converters/for_loops_test.py +++ b/tensorflow/contrib/autograph/converters/for_loops_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import for_loops +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import for_loops from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/ifexp.py b/tensorflow/contrib/autograph/converters/ifexp.py similarity index 88% rename from tensorflow/contrib/py2tf/converters/ifexp.py rename to tensorflow/contrib/autograph/converters/ifexp.py index 5fd6f348af..aff94d2b79 100644 --- a/tensorflow/contrib/py2tf/converters/ifexp.py +++ b/tensorflow/contrib/autograph/converters/ifexp.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer class IfExp(transformer.Base): @@ -27,7 +27,7 @@ class IfExp(transformer.Base): def visit_IfExp(self, node): template = """ - py2tf_utils.run_cond(test, lambda: body, lambda: orelse) + autograph_utils.run_cond(test, lambda: body, lambda: orelse) """ desugared_ifexp = templates.replace_as_expression( template, test=node.test, body=node.body, orelse=node.orelse) diff --git a/tensorflow/contrib/py2tf/converters/ifexp_test.py b/tensorflow/contrib/autograph/converters/ifexp_test.py similarity index 86% rename from tensorflow/contrib/py2tf/converters/ifexp_test.py rename to tensorflow/contrib/autograph/converters/ifexp_test.py index 9c357ef35b..ac6849dcb4 100644 --- a/tensorflow/contrib/py2tf/converters/ifexp_test.py +++ b/tensorflow/contrib/autograph/converters/ifexp_test.py @@ -18,9 +18,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import ifexp +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import ifexp from tensorflow.python.platform import test @@ -38,7 +38,7 @@ class IfExpTest(converter_test_base.TestCase): return 1 if x else 0 with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils for x in [0, 1]: self.assertEqual(test_fn(x), result.test_fn(x)) @@ -52,7 +52,7 @@ class IfExpTest(converter_test_base.TestCase): return y with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils result.f = f for x in [-2, 2]: self.assertEqual(test_fn(x), result.test_fn(x)) @@ -63,7 +63,7 @@ class IfExpTest(converter_test_base.TestCase): return x * x if x > 0 else x with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils for x in [-2, 2]: self.assertEqual(test_fn(x), result.test_fn(x)) @@ -73,7 +73,7 @@ class IfExpTest(converter_test_base.TestCase): return x * x if x > 0 else x if x else 1 with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils for x in [-2, 0, 2]: self.assertEqual(test_fn(x), result.test_fn(x)) @@ -85,7 +85,7 @@ class IfExpTest(converter_test_base.TestCase): return -x with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils for x in [-2, 2, 5]: self.assertEqual(test_fn(x), result.test_fn(x)) @@ -97,7 +97,7 @@ class IfExpTest(converter_test_base.TestCase): return x with self.compiled_fn(test_fn) as result: - result.py2tf_util = utils + result.autograph_util = utils for x in [-2, 2, 5]: self.assertEqual(test_fn(x), result.test_fn(x)) diff --git a/tensorflow/contrib/py2tf/converters/list_comprehension.py b/tensorflow/contrib/autograph/converters/list_comprehension.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/list_comprehension.py rename to tensorflow/contrib/autograph/converters/list_comprehension.py index e874483110..d7f2920151 100644 --- a/tensorflow/contrib/py2tf/converters/list_comprehension.py +++ b/tensorflow/contrib/autograph/converters/list_comprehension.py @@ -31,9 +31,9 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer class ListCompCanonicalizationTransformer(transformer.Base): diff --git a/tensorflow/contrib/py2tf/converters/list_comprehension_test.py b/tensorflow/contrib/autograph/converters/list_comprehension_test.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/list_comprehension_test.py rename to tensorflow/contrib/autograph/converters/list_comprehension_test.py index 025fac11e4..4758671f5e 100644 --- a/tensorflow/contrib/py2tf/converters/list_comprehension_test.py +++ b/tensorflow/contrib/autograph/converters/list_comprehension_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import list_comprehension +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import list_comprehension from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/lists.py b/tensorflow/contrib/autograph/converters/lists.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/lists.py rename to tensorflow/contrib/autograph/converters/lists.py index 3e62037a50..234a0a7487 100644 --- a/tensorflow/contrib/py2tf/converters/lists.py +++ b/tensorflow/contrib/autograph/converters/lists.py @@ -32,9 +32,9 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer from tensorflow.python.framework import dtypes @@ -74,7 +74,7 @@ class ListTransformer(transformer.Base): if qn.qn[-1] == 'append' and (len(call_node.args) == 1): template = """ - target = py2tf_utils.dynamic_list_append(target, element) + target = autograph_utils.dynamic_list_append(target, element) """ node = templates.replace( template, diff --git a/tensorflow/contrib/py2tf/converters/lists_test.py b/tensorflow/contrib/autograph/converters/lists_test.py similarity index 90% rename from tensorflow/contrib/py2tf/converters/lists_test.py rename to tensorflow/contrib/autograph/converters/lists_test.py index 671a1cc7b1..749ba14347 100644 --- a/tensorflow/contrib/py2tf/converters/lists_test.py +++ b/tensorflow/contrib/autograph/converters/lists_test.py @@ -18,9 +18,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import lists +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import lists from tensorflow.python.framework import dtypes from tensorflow.python.ops import tensor_array_ops from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/logical_expressions.py b/tensorflow/contrib/autograph/converters/logical_expressions.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/logical_expressions.py rename to tensorflow/contrib/autograph/converters/logical_expressions.py index e0abf74ebc..3a795a315a 100644 --- a/tensorflow/contrib/py2tf/converters/logical_expressions.py +++ b/tensorflow/contrib/autograph/converters/logical_expressions.py @@ -23,10 +23,10 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer # TODO(mdan): Properly extrack boolean ops according to lazy eval rules. @@ -57,8 +57,8 @@ class LogicalExpressionTransformer(transformer.Base): gast.NotEq: 'tf.not_equal', gast.Or: 'tf.logical_or', gast.USub: 'tf.negative', - gast.Is: 'py2tf_utils.dynamic_is', - gast.IsNot: 'py2tf_utils.dynamic_is_not' + gast.Is: 'autograph_utils.dynamic_is', + gast.IsNot: 'autograph_utils.dynamic_is_not' } def _expect_simple_symbol(self, operand): diff --git a/tensorflow/contrib/py2tf/converters/logical_expressions_test.py b/tensorflow/contrib/autograph/converters/logical_expressions_test.py similarity index 92% rename from tensorflow/contrib/py2tf/converters/logical_expressions_test.py rename to tensorflow/contrib/autograph/converters/logical_expressions_test.py index eb28c309a4..2814060c4d 100644 --- a/tensorflow/contrib/py2tf/converters/logical_expressions_test.py +++ b/tensorflow/contrib/autograph/converters/logical_expressions_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import logical_expressions +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import logical_expressions from tensorflow.python.ops import math_ops from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/name_scopes.py b/tensorflow/contrib/autograph/converters/name_scopes.py similarity index 93% rename from tensorflow/contrib/py2tf/converters/name_scopes.py rename to tensorflow/contrib/autograph/converters/name_scopes.py index c702823fcf..2a3f474360 100644 --- a/tensorflow/contrib/py2tf/converters/name_scopes.py +++ b/tensorflow/contrib/autograph/converters/name_scopes.py @@ -21,8 +21,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer class FunctionNameScopeTransformer(transformer.Base): diff --git a/tensorflow/contrib/py2tf/converters/name_scopes_test.py b/tensorflow/contrib/autograph/converters/name_scopes_test.py similarity index 95% rename from tensorflow/contrib/py2tf/converters/name_scopes_test.py rename to tensorflow/contrib/autograph/converters/name_scopes_test.py index a8ca341602..61e5db2af8 100644 --- a/tensorflow/contrib/py2tf/converters/name_scopes_test.py +++ b/tensorflow/contrib/autograph/converters/name_scopes_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import name_scopes +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import name_scopes from tensorflow.python.framework import constant_op from tensorflow.python.framework import ops from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/converters/side_effect_guards.py b/tensorflow/contrib/autograph/converters/side_effect_guards.py similarity index 91% rename from tensorflow/contrib/py2tf/converters/side_effect_guards.py rename to tensorflow/contrib/autograph/converters/side_effect_guards.py index 30976b3ec6..1c1293d2c4 100644 --- a/tensorflow/contrib/py2tf/converters/side_effect_guards.py +++ b/tensorflow/contrib/autograph/converters/side_effect_guards.py @@ -36,12 +36,12 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class SymbolNamer(object): @@ -160,8 +160,8 @@ class SideEffectGuardTransformer(transformer.Base): [alias_map.get(s, s).ast() for s in guarded_args], None) template = """ - with py2tf_utils.control_dependency_on_returns(call): - aliased_guarded_args = py2tf_utils.alias_tensors(guarded_args) + with autograph_utils.control_dependency_on_returns(call): + aliased_guarded_args = autograph_utils.alias_tensors(guarded_args) """ control_deps_guard = templates.replace( template, @@ -172,7 +172,7 @@ class SideEffectGuardTransformer(transformer.Base): alias_map = {} template = """ - with py2tf_utils.control_dependency_on_returns(call): + with autograph_utils.control_dependency_on_returns(call): pass """ control_deps_guard = templates.replace(template, call=node.value)[-1] diff --git a/tensorflow/contrib/py2tf/converters/side_effect_guards_test.py b/tensorflow/contrib/autograph/converters/side_effect_guards_test.py similarity index 97% rename from tensorflow/contrib/py2tf/converters/side_effect_guards_test.py rename to tensorflow/contrib/autograph/converters/side_effect_guards_test.py index 463db2e770..ce0ce33243 100644 --- a/tensorflow/contrib/py2tf/converters/side_effect_guards_test.py +++ b/tensorflow/contrib/autograph/converters/side_effect_guards_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import side_effect_guards +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import side_effect_guards from tensorflow.python.framework import constant_op from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops diff --git a/tensorflow/contrib/py2tf/converters/single_return.py b/tensorflow/contrib/autograph/converters/single_return.py similarity index 96% rename from tensorflow/contrib/py2tf/converters/single_return.py rename to tensorflow/contrib/autograph/converters/single_return.py index 1194b98f5e..bcc9ca9dfe 100644 --- a/tensorflow/contrib/py2tf/converters/single_return.py +++ b/tensorflow/contrib/autograph/converters/single_return.py @@ -20,11 +20,11 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import templates -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import templates +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno # TODO(mdan): Move this logic into transformer_base. @@ -232,7 +232,7 @@ class DetectReturnInUnsupportedControlFlow(gast.NodeVisitor): def visit_Return(self, node): if self.cant_return: raise ValueError( - 'Pyflow currently does not support `return` statements in loops. ' + '`return` statements are not supported in loops. ' 'Try assigning to a variable in the while loop, and returning ' 'outside of the loop') diff --git a/tensorflow/contrib/py2tf/converters/single_return_test.py b/tensorflow/contrib/autograph/converters/single_return_test.py similarity index 97% rename from tensorflow/contrib/py2tf/converters/single_return_test.py rename to tensorflow/contrib/autograph/converters/single_return_test.py index 2ea7a9d6d3..d483005a09 100644 --- a/tensorflow/contrib/py2tf/converters/single_return_test.py +++ b/tensorflow/contrib/autograph/converters/single_return_test.py @@ -18,8 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.converters import converter_test_base -from tensorflow.contrib.py2tf.converters import single_return +from tensorflow.contrib.autograph.converters import converter_test_base +from tensorflow.contrib.autograph.converters import single_return from tensorflow.python.framework.ops import name_scope from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/impl/BUILD b/tensorflow/contrib/autograph/impl/BUILD similarity index 82% rename from tensorflow/contrib/py2tf/impl/BUILD rename to tensorflow/contrib/autograph/impl/BUILD index cc49d71b78..e468176da1 100644 --- a/tensorflow/contrib/py2tf/impl/BUILD +++ b/tensorflow/contrib/autograph/impl/BUILD @@ -25,10 +25,10 @@ py_library( srcs_version = "PY2AND3", visibility = ["//tensorflow:__subpackages__"], deps = [ - "//tensorflow/contrib/py2tf/converters", - "//tensorflow/contrib/py2tf/pyct", - "//tensorflow/contrib/py2tf/pyct/static_analysis", - "//tensorflow/contrib/py2tf/utils", + "//tensorflow/contrib/autograph/converters", + "//tensorflow/contrib/autograph/pyct", + "//tensorflow/contrib/autograph/pyct/static_analysis", + "//tensorflow/contrib/autograph/utils", "@gast_archive//:gast", "@six_archive//:six", ], @@ -40,7 +40,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":impl", - "//tensorflow/contrib/py2tf/utils", + "//tensorflow/contrib/autograph/utils", "//tensorflow/python:client_testlib", "//third_party/py/numpy", ], diff --git a/tensorflow/contrib/py2tf/impl/api.py b/tensorflow/contrib/autograph/impl/api.py similarity index 95% rename from tensorflow/contrib/py2tf/impl/api.py rename to tensorflow/contrib/autograph/impl/api.py index a9e8ea2043..1c4fcaa622 100644 --- a/tensorflow/contrib/py2tf/impl/api.py +++ b/tensorflow/contrib/autograph/impl/api.py @@ -27,13 +27,13 @@ import gast import six # pylint:enable=g-bad-import-order -from tensorflow.contrib.py2tf.impl import config -from tensorflow.contrib.py2tf.impl import conversion -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import inspect_utils -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.utils import builtins -from tensorflow.contrib.py2tf.utils import py_func +from tensorflow.contrib.autograph.impl import config +from tensorflow.contrib.autograph.impl import conversion +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import inspect_utils +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.utils import builtins +from tensorflow.contrib.autograph.utils import py_func from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util import tf_inspect @@ -89,7 +89,7 @@ def do_not_convert(run_as=RunMode.GRAPH, return_dtypes=None): Args: run_as: RunMode value. Whether to run the function as-is, or wrap it into a py_func. - return_dtypes: See py2tf.utils.py_func.wrap_py_func. Setting to None or + return_dtypes: See autograph.utils.py_func.wrap_py_func. Setting to None or empty list or tuple will create a dummy return value that can be used to set control dependencies. diff --git a/tensorflow/contrib/py2tf/impl/api_test.py b/tensorflow/contrib/autograph/impl/api_test.py similarity index 92% rename from tensorflow/contrib/py2tf/impl/api_test.py rename to tensorflow/contrib/autograph/impl/api_test.py index a7b1aba852..ee2d301d75 100644 --- a/tensorflow/contrib/py2tf/impl/api_test.py +++ b/tensorflow/contrib/autograph/impl/api_test.py @@ -20,11 +20,11 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.impl import api -from tensorflow.contrib.py2tf.impl import config -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.utils import py_func +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.impl import api +from tensorflow.contrib.autograph.impl import config +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.utils import py_func from tensorflow.python.framework import constant_op from tensorflow.python.platform import test @@ -37,10 +37,8 @@ class ApiTest(test.TestCase): def setUp(self): config.COMPILED_IMPORT_STATEMENTS = ( 'from __future__ import print_function', - 'from tensorflow.contrib.py2tf import utils as ' - 'py2tf_utils', - 'tf = py2tf_utils.fake_tf()' - ) + 'from tensorflow.contrib.autograph import utils as ' + 'autograph_utils', 'tf = autograph_utils.fake_tf()') def test_decorator_recurses(self): @@ -200,7 +198,7 @@ class ApiTest(test.TestCase): compiled_code = api.to_code(test_fn) # Just check for some key words and that it is parseable Python code. - self.assertRegexpMatches(compiled_code, 'py2tf_utils\\.run_while') + self.assertRegexpMatches(compiled_code, 'autograph_utils\\.run_while') self.assertIsNotNone(parser.parse_str(compiled_code)) diff --git a/tensorflow/contrib/py2tf/impl/config.py b/tensorflow/contrib/autograph/impl/config.py similarity index 79% rename from tensorflow/contrib/py2tf/impl/config.py rename to tensorflow/contrib/autograph/impl/config.py index bdbc6663dd..543c1486e6 100644 --- a/tensorflow/contrib/py2tf/impl/config.py +++ b/tensorflow/contrib/autograph/impl/config.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf import utils +from tensorflow.contrib.autograph import utils PYTHON_LITERALS = { @@ -35,16 +35,16 @@ DEFAULT_UNCOMPILED_MODULES = set(( # All of tensorflow's subpackages. Unlike the root tf module, they don't # have well-known names. Not refering to the module directly to avoid # circular imports. - (utils.__name__[:-len('.contrib.py2tf.utils')],), + ( + utils.__name__[:-len('.contrib.autograph.utils')],), )) NO_SIDE_EFFECT_CONSTRUCTORS = set(('tensorflow',)) # TODO(mdan): Also allow controlling the generated names (for testability). COMPILED_IMPORT_STATEMENTS = ( - 'from __future__ import print_function', - 'import tensorflow as tf', - 'from tensorflow.contrib.py2tf.impl import api as ' - 'py2tf_api', - 'from tensorflow.contrib.py2tf import utils as ' - 'py2tf_utils') + 'from __future__ import print_function', 'import tensorflow as tf', + 'from tensorflow.contrib.autograph.impl import api as ' + 'autograph_api', + 'from tensorflow.contrib.autograph import utils as ' + 'autograph_utils') diff --git a/tensorflow/contrib/py2tf/impl/conversion.py b/tensorflow/contrib/autograph/impl/conversion.py similarity index 84% rename from tensorflow/contrib/py2tf/impl/conversion.py rename to tensorflow/contrib/autograph/impl/conversion.py index 37b24ab55f..62a49cd92d 100644 --- a/tensorflow/contrib/py2tf/impl/conversion.py +++ b/tensorflow/contrib/autograph/impl/conversion.py @@ -20,31 +20,31 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.converters import asserts -from tensorflow.contrib.py2tf.converters import break_statements -from tensorflow.contrib.py2tf.converters import builtin_functions -from tensorflow.contrib.py2tf.converters import call_trees -from tensorflow.contrib.py2tf.converters import continue_statements -from tensorflow.contrib.py2tf.converters import control_flow -from tensorflow.contrib.py2tf.converters import decorators -from tensorflow.contrib.py2tf.converters import for_loops -from tensorflow.contrib.py2tf.converters import ifexp -from tensorflow.contrib.py2tf.converters import lists -from tensorflow.contrib.py2tf.converters import logical_expressions -from tensorflow.contrib.py2tf.converters import name_scopes -from tensorflow.contrib.py2tf.converters import side_effect_guards -from tensorflow.contrib.py2tf.converters import single_return -from tensorflow.contrib.py2tf.impl import config -from tensorflow.contrib.py2tf.impl import naming -from tensorflow.contrib.py2tf.pyct import context -from tensorflow.contrib.py2tf.pyct import inspect_utils -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.static_analysis import activity -from tensorflow.contrib.py2tf.pyct.static_analysis import live_values -from tensorflow.contrib.py2tf.pyct.static_analysis import type_info -from tensorflow.contrib.py2tf.utils import type_hints +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.converters import asserts +from tensorflow.contrib.autograph.converters import break_statements +from tensorflow.contrib.autograph.converters import builtin_functions +from tensorflow.contrib.autograph.converters import call_trees +from tensorflow.contrib.autograph.converters import continue_statements +from tensorflow.contrib.autograph.converters import control_flow +from tensorflow.contrib.autograph.converters import decorators +from tensorflow.contrib.autograph.converters import for_loops +from tensorflow.contrib.autograph.converters import ifexp +from tensorflow.contrib.autograph.converters import lists +from tensorflow.contrib.autograph.converters import logical_expressions +from tensorflow.contrib.autograph.converters import name_scopes +from tensorflow.contrib.autograph.converters import side_effect_guards +from tensorflow.contrib.autograph.converters import single_return +from tensorflow.contrib.autograph.impl import config +from tensorflow.contrib.autograph.impl import naming +from tensorflow.contrib.autograph.pyct import context +from tensorflow.contrib.autograph.pyct import inspect_utils +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.static_analysis import activity +from tensorflow.contrib.autograph.pyct.static_analysis import live_values +from tensorflow.contrib.autograph.pyct.static_analysis import type_info +from tensorflow.contrib.autograph.utils import type_hints from tensorflow.python.util import tf_inspect @@ -213,19 +213,19 @@ def class_to_graph(c, conversion_map): def _add_self_references(namespace, api_module): """Self refs are only required for analysis and are not used directly.""" # Manually add the utils namespace which may be used from generated code. - if 'py2tf_util' not in namespace: - namespace['py2tf_utils'] = utils - elif namespace['py2tf_utils'] != utils: + if 'autograph_util' not in namespace: + namespace['autograph_utils'] = utils + elif namespace['autograph_utils'] != utils: raise ValueError( - 'The module name "py2tf_utils" is reserved and may not be used.') + 'The module name "autograph_utils" is reserved and may not be used.') # We also make reference to the api module for dynamic conversion, but # to avoid circular references we don't import it here. - if 'py2tf_api' not in namespace: - namespace['py2tf_api'] = api_module - elif namespace['py2tf_api'] != api_module: + if 'autograph_api' not in namespace: + namespace['autograph_api'] = api_module + elif namespace['autograph_api'] != api_module: raise ValueError( - 'The module name "py2tf_api" is reserved and may not be used.') + 'The module name "autograph_api" is reserved and may not be used.') def function_to_graph(f, conversion_map, arg_values, arg_types, diff --git a/tensorflow/contrib/py2tf/impl/conversion_test.py b/tensorflow/contrib/autograph/impl/conversion_test.py similarity index 96% rename from tensorflow/contrib/py2tf/impl/conversion_test.py rename to tensorflow/contrib/autograph/impl/conversion_test.py index 9ff256aace..7066739eb8 100644 --- a/tensorflow/contrib/py2tf/impl/conversion_test.py +++ b/tensorflow/contrib/autograph/impl/conversion_test.py @@ -20,8 +20,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.impl import conversion +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.impl import conversion from tensorflow.python.framework import constant_op from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/impl/naming.py b/tensorflow/contrib/autograph/impl/naming.py similarity index 98% rename from tensorflow/contrib/py2tf/impl/naming.py rename to tensorflow/contrib/autograph/impl/naming.py index 51326091de..1facaa0ca0 100644 --- a/tensorflow/contrib/py2tf/impl/naming.py +++ b/tensorflow/contrib/autograph/impl/naming.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.pyct import qual_names +from tensorflow.contrib.autograph.pyct import qual_names class Namer(object): diff --git a/tensorflow/contrib/py2tf/impl/naming_test.py b/tensorflow/contrib/autograph/impl/naming_test.py similarity index 98% rename from tensorflow/contrib/py2tf/impl/naming_test.py rename to tensorflow/contrib/autograph/impl/naming_test.py index beb4e54937..73fc089465 100644 --- a/tensorflow/contrib/py2tf/impl/naming_test.py +++ b/tensorflow/contrib/autograph/impl/naming_test.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.impl import naming +from tensorflow.contrib.autograph.impl import naming from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/BUILD b/tensorflow/contrib/autograph/pyct/BUILD similarity index 100% rename from tensorflow/contrib/py2tf/pyct/BUILD rename to tensorflow/contrib/autograph/pyct/BUILD diff --git a/tensorflow/contrib/py2tf/pyct/__init__.py b/tensorflow/contrib/autograph/pyct/__init__.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/__init__.py rename to tensorflow/contrib/autograph/pyct/__init__.py diff --git a/tensorflow/contrib/py2tf/pyct/anno.py b/tensorflow/contrib/autograph/pyct/anno.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/anno.py rename to tensorflow/contrib/autograph/pyct/anno.py diff --git a/tensorflow/contrib/py2tf/pyct/anno_test.py b/tensorflow/contrib/autograph/pyct/anno_test.py similarity index 97% rename from tensorflow/contrib/py2tf/pyct/anno_test.py rename to tensorflow/contrib/autograph/pyct/anno_test.py index 6c29918fdf..1d4d9d119e 100644 --- a/tensorflow/contrib/py2tf/pyct/anno_test.py +++ b/tensorflow/contrib/autograph/pyct/anno_test.py @@ -20,7 +20,7 @@ from __future__ import print_function import ast -from tensorflow.contrib.py2tf.pyct import anno +from tensorflow.contrib.autograph.pyct import anno from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/ast_util.py b/tensorflow/contrib/autograph/pyct/ast_util.py similarity index 98% rename from tensorflow/contrib/py2tf/pyct/ast_util.py rename to tensorflow/contrib/autograph/pyct/ast_util.py index 6f7e656c26..5a41b5e4a9 100644 --- a/tensorflow/contrib/py2tf/pyct/ast_util.py +++ b/tensorflow/contrib/autograph/pyct/ast_util.py @@ -22,7 +22,7 @@ import ast import gast -from tensorflow.contrib.py2tf.pyct import anno +from tensorflow.contrib.autograph.pyct import anno class CleanCopier(gast.NodeVisitor): diff --git a/tensorflow/contrib/py2tf/pyct/ast_util_test.py b/tensorflow/contrib/autograph/pyct/ast_util_test.py similarity index 93% rename from tensorflow/contrib/py2tf/pyct/ast_util_test.py rename to tensorflow/contrib/autograph/pyct/ast_util_test.py index 8d123679e3..8faf92c705 100644 --- a/tensorflow/contrib/py2tf/pyct/ast_util_test.py +++ b/tensorflow/contrib/autograph/pyct/ast_util_test.py @@ -20,10 +20,10 @@ from __future__ import print_function import ast -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/compiler.py b/tensorflow/contrib/autograph/pyct/compiler.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/compiler.py rename to tensorflow/contrib/autograph/pyct/compiler.py diff --git a/tensorflow/contrib/py2tf/pyct/compiler_test.py b/tensorflow/contrib/autograph/pyct/compiler_test.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/compiler_test.py rename to tensorflow/contrib/autograph/pyct/compiler_test.py index 243f4c8153..98cdc1506b 100644 --- a/tensorflow/contrib/py2tf/pyct/compiler_test.py +++ b/tensorflow/contrib/autograph/pyct/compiler_test.py @@ -22,8 +22,8 @@ import textwrap import gast -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import parser +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import parser from tensorflow.python.platform import test from tensorflow.python.util import tf_inspect diff --git a/tensorflow/contrib/py2tf/pyct/context.py b/tensorflow/contrib/autograph/pyct/context.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/context.py rename to tensorflow/contrib/autograph/pyct/context.py diff --git a/tensorflow/contrib/py2tf/pyct/inspect_utils.py b/tensorflow/contrib/autograph/pyct/inspect_utils.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/inspect_utils.py rename to tensorflow/contrib/autograph/pyct/inspect_utils.py diff --git a/tensorflow/contrib/py2tf/pyct/inspect_utils_test.py b/tensorflow/contrib/autograph/pyct/inspect_utils_test.py similarity index 98% rename from tensorflow/contrib/py2tf/pyct/inspect_utils_test.py rename to tensorflow/contrib/autograph/pyct/inspect_utils_test.py index 5528ac851f..ddca6f963b 100644 --- a/tensorflow/contrib/py2tf/pyct/inspect_utils_test.py +++ b/tensorflow/contrib/autograph/pyct/inspect_utils_test.py @@ -22,7 +22,7 @@ from functools import wraps import six -from tensorflow.contrib.py2tf.pyct import inspect_utils +from tensorflow.contrib.autograph.pyct import inspect_utils from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/parser.py b/tensorflow/contrib/autograph/pyct/parser.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/parser.py rename to tensorflow/contrib/autograph/pyct/parser.py diff --git a/tensorflow/contrib/py2tf/pyct/parser_test.py b/tensorflow/contrib/autograph/pyct/parser_test.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/parser_test.py rename to tensorflow/contrib/autograph/pyct/parser_test.py index c58ffc7e0c..007a4c6fb0 100644 --- a/tensorflow/contrib/py2tf/pyct/parser_test.py +++ b/tensorflow/contrib/autograph/pyct/parser_test.py @@ -20,7 +20,7 @@ from __future__ import print_function import textwrap -from tensorflow.contrib.py2tf.pyct import parser +from tensorflow.contrib.autograph.pyct import parser from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/pretty_printer.py b/tensorflow/contrib/autograph/pyct/pretty_printer.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/pretty_printer.py rename to tensorflow/contrib/autograph/pyct/pretty_printer.py diff --git a/tensorflow/contrib/py2tf/pyct/pretty_printer_test.py b/tensorflow/contrib/autograph/pyct/pretty_printer_test.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/pretty_printer_test.py rename to tensorflow/contrib/autograph/pyct/pretty_printer_test.py index 81e3f47b80..0cb48f3576 100644 --- a/tensorflow/contrib/py2tf/pyct/pretty_printer_test.py +++ b/tensorflow/contrib/autograph/pyct/pretty_printer_test.py @@ -20,7 +20,7 @@ from __future__ import print_function import ast -from tensorflow.contrib.py2tf.pyct import pretty_printer +from tensorflow.contrib.autograph.pyct import pretty_printer from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/qual_names.py b/tensorflow/contrib/autograph/pyct/qual_names.py similarity index 99% rename from tensorflow/contrib/py2tf/pyct/qual_names.py rename to tensorflow/contrib/autograph/pyct/qual_names.py index 7dec13db92..4d5764a974 100644 --- a/tensorflow/contrib/py2tf/pyct/qual_names.py +++ b/tensorflow/contrib/autograph/pyct/qual_names.py @@ -29,7 +29,7 @@ import collections import gast -from tensorflow.contrib.py2tf.pyct import anno +from tensorflow.contrib.autograph.pyct import anno class Symbol(collections.namedtuple('Symbol', ['name'])): diff --git a/tensorflow/contrib/py2tf/pyct/qual_names_test.py b/tensorflow/contrib/autograph/pyct/qual_names_test.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/qual_names_test.py rename to tensorflow/contrib/autograph/pyct/qual_names_test.py index 6583fa243b..103bd25aa3 100644 --- a/tensorflow/contrib/py2tf/pyct/qual_names_test.py +++ b/tensorflow/contrib/autograph/pyct/qual_names_test.py @@ -20,11 +20,11 @@ from __future__ import print_function import textwrap -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.qual_names import QN -from tensorflow.contrib.py2tf.pyct.qual_names import resolve +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.qual_names import QN +from tensorflow.contrib.autograph.pyct.qual_names import resolve from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/BUILD b/tensorflow/contrib/autograph/pyct/static_analysis/BUILD similarity index 83% rename from tensorflow/contrib/py2tf/pyct/static_analysis/BUILD rename to tensorflow/contrib/autograph/pyct/static_analysis/BUILD index 2799b56a00..d192bc7aab 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/BUILD +++ b/tensorflow/contrib/autograph/pyct/static_analysis/BUILD @@ -25,7 +25,7 @@ py_library( srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "@gast_archive//:gast", ], ) @@ -36,7 +36,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":static_analysis", - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "//tensorflow/python:client_testlib", "@gast_archive//:gast", ], @@ -48,7 +48,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":static_analysis", - "//tensorflow/contrib/py2tf/pyct", + "//tensorflow/contrib/autograph/pyct", "//tensorflow/python:client_testlib", ], ) @@ -59,8 +59,8 @@ py_test( srcs_version = "PY2AND3", deps = [ ":static_analysis", - "//tensorflow/contrib/py2tf/pyct", - "//tensorflow/contrib/py2tf/utils", + "//tensorflow/contrib/autograph/pyct", + "//tensorflow/contrib/autograph/utils", "//tensorflow/python:client_testlib", ], ) diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/__init__.py b/tensorflow/contrib/autograph/pyct/static_analysis/__init__.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/static_analysis/__init__.py rename to tensorflow/contrib/autograph/pyct/static_analysis/__init__.py diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py b/tensorflow/contrib/autograph/pyct/static_analysis/activity.py similarity index 97% rename from tensorflow/contrib/py2tf/pyct/static_analysis/activity.py rename to tensorflow/contrib/autograph/pyct/static_analysis/activity.py index 716672a53b..da6a2f6f05 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/activity.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/activity.py @@ -22,10 +22,10 @@ import copy import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.qual_names import QN -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.qual_names import QN +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno # TODO(mdan): Add support for PY3 (e.g. Param vs arg). diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/activity_test.py b/tensorflow/contrib/autograph/pyct/static_analysis/activity_test.py similarity index 95% rename from tensorflow/contrib/py2tf/pyct/static_analysis/activity_test.py rename to tensorflow/contrib/autograph/pyct/static_analysis/activity_test.py index b16d15b39d..37c28872bb 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/activity_test.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/activity_test.py @@ -20,13 +20,13 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import context -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.qual_names import QN -from tensorflow.contrib.py2tf.pyct.static_analysis import activity -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import context +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.qual_names import QN +from tensorflow.contrib.autograph.pyct.static_analysis import activity +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/annos.py b/tensorflow/contrib/autograph/pyct/static_analysis/annos.py similarity index 100% rename from tensorflow/contrib/py2tf/pyct/static_analysis/annos.py rename to tensorflow/contrib/autograph/pyct/static_analysis/annos.py diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/live_values.py b/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/static_analysis/live_values.py rename to tensorflow/contrib/autograph/pyct/static_analysis/live_values.py index ac5697900a..5f813355e6 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/live_values.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py @@ -25,9 +25,9 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import transformer -from tensorflow.contrib.py2tf.pyct.static_analysis.annos import NodeAnno +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import transformer +from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class LiveValueResolver(transformer.Base): diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/live_values_test.py b/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py similarity index 89% rename from tensorflow/contrib/py2tf/pyct/static_analysis/live_values_test.py rename to tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py index a56dff824e..b66439624e 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/live_values_test.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py @@ -18,13 +18,13 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import context -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.static_analysis import activity -from tensorflow.contrib.py2tf.pyct.static_analysis import live_values -from tensorflow.contrib.py2tf.pyct.static_analysis import type_info +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import context +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.static_analysis import activity +from tensorflow.contrib.autograph.pyct.static_analysis import live_values +from tensorflow.contrib.autograph.pyct.static_analysis import type_info from tensorflow.python.framework import constant_op from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py b/tensorflow/contrib/autograph/pyct/static_analysis/type_info.py similarity index 98% rename from tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py rename to tensorflow/contrib/autograph/pyct/static_analysis/type_info.py index a969adbeca..203aa3c3d1 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/type_info.py @@ -43,8 +43,8 @@ from __future__ import print_function import gast -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import transformer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import transformer from tensorflow.python.util import tf_inspect diff --git a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py b/tensorflow/contrib/autograph/pyct/static_analysis/type_info_test.py similarity index 93% rename from tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py rename to tensorflow/contrib/autograph/pyct/static_analysis/type_info_test.py index 8a8956197d..c0de4a6043 100644 --- a/tensorflow/contrib/py2tf/pyct/static_analysis/type_info_test.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/type_info_test.py @@ -18,14 +18,14 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf import utils -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import context -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names -from tensorflow.contrib.py2tf.pyct.static_analysis import activity -from tensorflow.contrib.py2tf.pyct.static_analysis import live_values -from tensorflow.contrib.py2tf.pyct.static_analysis import type_info +from tensorflow.contrib.autograph import utils +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import context +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names +from tensorflow.contrib.autograph.pyct.static_analysis import activity +from tensorflow.contrib.autograph.pyct.static_analysis import live_values +from tensorflow.contrib.autograph.pyct.static_analysis import type_info from tensorflow.python.client import session from tensorflow.python.platform import test from tensorflow.python.training import training diff --git a/tensorflow/contrib/py2tf/pyct/templates.py b/tensorflow/contrib/autograph/pyct/templates.py similarity index 98% rename from tensorflow/contrib/py2tf/pyct/templates.py rename to tensorflow/contrib/autograph/pyct/templates.py index 590be68234..fb99e0d4e5 100644 --- a/tensorflow/contrib/py2tf/pyct/templates.py +++ b/tensorflow/contrib/autograph/pyct/templates.py @@ -26,9 +26,9 @@ import textwrap import gast -from tensorflow.contrib.py2tf.pyct import ast_util -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import qual_names +from tensorflow.contrib.autograph.pyct import ast_util +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import qual_names class ReplaceTransformer(gast.NodeTransformer): diff --git a/tensorflow/contrib/py2tf/pyct/templates_test.py b/tensorflow/contrib/autograph/pyct/templates_test.py similarity index 96% rename from tensorflow/contrib/py2tf/pyct/templates_test.py rename to tensorflow/contrib/autograph/pyct/templates_test.py index af939caf32..a01f8bf04c 100644 --- a/tensorflow/contrib/py2tf/pyct/templates_test.py +++ b/tensorflow/contrib/autograph/pyct/templates_test.py @@ -22,9 +22,9 @@ import imp import gast -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import parser -from tensorflow.contrib.py2tf.pyct import templates +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import parser +from tensorflow.contrib.autograph.pyct import templates from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/pyct/transformer.py b/tensorflow/contrib/autograph/pyct/transformer.py similarity index 89% rename from tensorflow/contrib/py2tf/pyct/transformer.py rename to tensorflow/contrib/autograph/pyct/transformer.py index 31ef7e1c05..35f114b6e1 100644 --- a/tensorflow/contrib/py2tf/pyct/transformer.py +++ b/tensorflow/contrib/autograph/pyct/transformer.py @@ -23,12 +23,12 @@ import sys import gast import six -from tensorflow.contrib.py2tf.pyct import anno -from tensorflow.contrib.py2tf.pyct import compiler -from tensorflow.contrib.py2tf.pyct import pretty_printer +from tensorflow.contrib.autograph.pyct import anno +from tensorflow.contrib.autograph.pyct import compiler +from tensorflow.contrib.autograph.pyct import pretty_printer -class PyFlowParseError(SyntaxError): +class AutographParseError(SyntaxError): pass @@ -77,8 +77,8 @@ class Base(gast.NodeTransformer): line = source_code.splitlines()[self._lineno - 1] else: line = '' - six.reraise(PyFlowParseError, - PyFlowParseError( + six.reraise(AutographParseError, + AutographParseError( msg, (source_file, self._lineno, self._col_offset + 1, line)), sys.exc_info()[2]) diff --git a/tensorflow/contrib/py2tf/utils/BUILD b/tensorflow/contrib/autograph/utils/BUILD similarity index 100% rename from tensorflow/contrib/py2tf/utils/BUILD rename to tensorflow/contrib/autograph/utils/BUILD diff --git a/tensorflow/contrib/autograph/utils/__init__.py b/tensorflow/contrib/autograph/utils/__init__.py new file mode 100644 index 0000000000..22898b17e9 --- /dev/null +++ b/tensorflow/contrib/autograph/utils/__init__.py @@ -0,0 +1,36 @@ +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Utility module that contains APIs usable in the generated code.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.autograph.utils.builtins import dynamic_builtin +from tensorflow.contrib.autograph.utils.builtins import dynamic_dataset +from tensorflow.contrib.autograph.utils.builtins import dynamic_for_cond +from tensorflow.contrib.autograph.utils.builtins import dynamic_print +from tensorflow.contrib.autograph.utils.builtins import dynamic_range +from tensorflow.contrib.autograph.utils.context_managers import control_dependency_on_returns +from tensorflow.contrib.autograph.utils.misc import alias_tensors +from tensorflow.contrib.autograph.utils.multiple_dispatch import dynamic_is +from tensorflow.contrib.autograph.utils.multiple_dispatch import dynamic_is_not +from tensorflow.contrib.autograph.utils.multiple_dispatch import run_cond +from tensorflow.contrib.autograph.utils.multiple_dispatch import run_while +from tensorflow.contrib.autograph.utils.py_func import wrap_py_func +from tensorflow.contrib.autograph.utils.tensor_list import dynamic_list_append +from tensorflow.contrib.autograph.utils.testing import fake_tf +from tensorflow.contrib.autograph.utils.type_check import is_tensor +from tensorflow.contrib.autograph.utils.type_hints import set_element_type diff --git a/tensorflow/contrib/py2tf/utils/builtins.py b/tensorflow/contrib/autograph/utils/builtins.py similarity index 98% rename from tensorflow/contrib/py2tf/utils/builtins.py rename to tensorflow/contrib/autograph/utils/builtins.py index 251b4ed8ee..4ab32ee47d 100644 --- a/tensorflow/contrib/py2tf/utils/builtins.py +++ b/tensorflow/contrib/autograph/utils/builtins.py @@ -20,8 +20,8 @@ from __future__ import print_function import six -from tensorflow.contrib.py2tf.utils import py_func -from tensorflow.contrib.py2tf.utils import type_check +from tensorflow.contrib.autograph.utils import py_func +from tensorflow.contrib.autograph.utils import type_check from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops diff --git a/tensorflow/contrib/py2tf/utils/builtins_test.py b/tensorflow/contrib/autograph/utils/builtins_test.py similarity index 98% rename from tensorflow/contrib/py2tf/utils/builtins_test.py rename to tensorflow/contrib/autograph/utils/builtins_test.py index 59b3573d38..d9f7913d89 100644 --- a/tensorflow/contrib/py2tf/utils/builtins_test.py +++ b/tensorflow/contrib/autograph/utils/builtins_test.py @@ -22,7 +22,7 @@ import sys import six -from tensorflow.contrib.py2tf.utils import builtins +from tensorflow.contrib.autograph.utils import builtins from tensorflow.python.framework import constant_op from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/utils/context_managers.py b/tensorflow/contrib/autograph/utils/context_managers.py similarity index 100% rename from tensorflow/contrib/py2tf/utils/context_managers.py rename to tensorflow/contrib/autograph/utils/context_managers.py diff --git a/tensorflow/contrib/py2tf/utils/context_managers_test.py b/tensorflow/contrib/autograph/utils/context_managers_test.py similarity index 96% rename from tensorflow/contrib/py2tf/utils/context_managers_test.py rename to tensorflow/contrib/autograph/utils/context_managers_test.py index 404f6e44e5..42e27724b9 100644 --- a/tensorflow/contrib/py2tf/utils/context_managers_test.py +++ b/tensorflow/contrib/autograph/utils/context_managers_test.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.utils import context_managers +from tensorflow.contrib.autograph.utils import context_managers from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.ops import tensor_array_ops diff --git a/tensorflow/contrib/py2tf/utils/misc.py b/tensorflow/contrib/autograph/utils/misc.py similarity index 100% rename from tensorflow/contrib/py2tf/utils/misc.py rename to tensorflow/contrib/autograph/utils/misc.py diff --git a/tensorflow/contrib/py2tf/utils/misc_test.py b/tensorflow/contrib/autograph/utils/misc_test.py similarity index 96% rename from tensorflow/contrib/py2tf/utils/misc_test.py rename to tensorflow/contrib/autograph/utils/misc_test.py index 8aedd4cd64..71e358c33e 100644 --- a/tensorflow/contrib/py2tf/utils/misc_test.py +++ b/tensorflow/contrib/autograph/utils/misc_test.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.utils.misc import alias_tensors +from tensorflow.contrib.autograph.utils.misc import alias_tensors from tensorflow.python.framework.constant_op import constant from tensorflow.python.ops.variables import Variable from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/utils/multiple_dispatch.py b/tensorflow/contrib/autograph/utils/multiple_dispatch.py similarity index 95% rename from tensorflow/contrib/py2tf/utils/multiple_dispatch.py rename to tensorflow/contrib/autograph/utils/multiple_dispatch.py index 427a936c35..b756ccfaee 100644 --- a/tensorflow/contrib/py2tf/utils/multiple_dispatch.py +++ b/tensorflow/contrib/autograph/utils/multiple_dispatch.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Utilities for type-dependent behavior used in py2tf-generated code.""" +"""Utilities for type-dependent behavior used in autograph-generated code.""" from __future__ import absolute_import from __future__ import division @@ -20,7 +20,7 @@ from __future__ import print_function import six -from tensorflow.contrib.py2tf.utils.type_check import is_tensor +from tensorflow.contrib.autograph.utils.type_check import is_tensor from tensorflow.python.ops import control_flow_ops diff --git a/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py b/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py similarity index 98% rename from tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py rename to tensorflow/contrib/autograph/utils/multiple_dispatch_test.py index 75e8fdd5ed..8c7daa6ded 100644 --- a/tensorflow/contrib/py2tf/utils/multiple_dispatch_test.py +++ b/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py @@ -20,7 +20,7 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.py2tf.utils import multiple_dispatch +from tensorflow.contrib.autograph.utils import multiple_dispatch from tensorflow.python.client.session import Session from tensorflow.python.framework.constant_op import constant from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/utils/py_func.py b/tensorflow/contrib/autograph/utils/py_func.py similarity index 97% rename from tensorflow/contrib/py2tf/utils/py_func.py rename to tensorflow/contrib/autograph/utils/py_func.py index 34f2a8b70b..11ebfb2e49 100644 --- a/tensorflow/contrib/py2tf/utils/py_func.py +++ b/tensorflow/contrib/autograph/utils/py_func.py @@ -118,9 +118,8 @@ def wrap_py_func(f, return_dtypes, args, kwargs=None, use_dummy_return=False): assert isinstance(return_dtypes, dtypes.DType) def f_wrapper(*tensor_args): - f_args = tuple( - tensor_args[tensor_args_idx[i]] if arg_is_tensor[i] else a - for i, a in enumerate(args)) + f_args = tuple(tensor_args[tensor_args_idx[i]] if arg_is_tensor[i] else a + for i, a in enumerate(args)) f_kwargs = { k: tensor_args[tensor_args_idx[k]] if kwarg_is_tensor[k] else kwargs[k] for i, k in enumerate(kwarg_keys) diff --git a/tensorflow/contrib/py2tf/utils/py_func_test.py b/tensorflow/contrib/autograph/utils/py_func_test.py similarity index 98% rename from tensorflow/contrib/py2tf/utils/py_func_test.py rename to tensorflow/contrib/autograph/utils/py_func_test.py index 3b7a35365a..2468263142 100644 --- a/tensorflow/contrib/py2tf/utils/py_func_test.py +++ b/tensorflow/contrib/autograph/utils/py_func_test.py @@ -18,7 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.utils import py_func +from tensorflow.contrib.autograph.utils import py_func from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/utils/tensor_list.py b/tensorflow/contrib/autograph/utils/tensor_list.py similarity index 100% rename from tensorflow/contrib/py2tf/utils/tensor_list.py rename to tensorflow/contrib/autograph/utils/tensor_list.py diff --git a/tensorflow/contrib/py2tf/utils/tensor_list_test.py b/tensorflow/contrib/autograph/utils/tensor_list_test.py similarity index 97% rename from tensorflow/contrib/py2tf/utils/tensor_list_test.py rename to tensorflow/contrib/autograph/utils/tensor_list_test.py index 110e4d105e..d58489eb68 100644 --- a/tensorflow/contrib/py2tf/utils/tensor_list_test.py +++ b/tensorflow/contrib/autograph/utils/tensor_list_test.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Tests for PyFlow list.""" +"""Tests for Autograph lists.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.contrib.py2tf.utils import tensor_list as tl +from tensorflow.contrib.autograph.utils import tensor_list as tl from tensorflow.python.client.session import Session from tensorflow.python.eager import context from tensorflow.python.framework import dtypes diff --git a/tensorflow/contrib/py2tf/utils/testing.py b/tensorflow/contrib/autograph/utils/testing.py similarity index 100% rename from tensorflow/contrib/py2tf/utils/testing.py rename to tensorflow/contrib/autograph/utils/testing.py diff --git a/tensorflow/contrib/py2tf/utils/type_check.py b/tensorflow/contrib/autograph/utils/type_check.py similarity index 95% rename from tensorflow/contrib/py2tf/utils/type_check.py rename to tensorflow/contrib/autograph/utils/type_check.py index b9b2b451a4..8748abc47b 100644 --- a/tensorflow/contrib/py2tf/utils/type_check.py +++ b/tensorflow/contrib/autograph/utils/type_check.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Utilities used in py2tf-generated code.""" +"""Utilities used in autograph-generated code.""" from __future__ import absolute_import from __future__ import division diff --git a/tensorflow/contrib/py2tf/utils/type_check_test.py b/tensorflow/contrib/autograph/utils/type_check_test.py similarity index 96% rename from tensorflow/contrib/py2tf/utils/type_check_test.py rename to tensorflow/contrib/autograph/utils/type_check_test.py index 7d0428e9cc..3b67b7194c 100644 --- a/tensorflow/contrib/py2tf/utils/type_check_test.py +++ b/tensorflow/contrib/autograph/utils/type_check_test.py @@ -20,7 +20,7 @@ from __future__ import print_function import numpy -from tensorflow.contrib.py2tf.utils import type_check +from tensorflow.contrib.autograph.utils import type_check from tensorflow.python.framework import constant_op from tensorflow.python.framework import test_util from tensorflow.python.platform import test diff --git a/tensorflow/contrib/py2tf/utils/type_hints.py b/tensorflow/contrib/autograph/utils/type_hints.py similarity index 100% rename from tensorflow/contrib/py2tf/utils/type_hints.py rename to tensorflow/contrib/autograph/utils/type_hints.py diff --git a/tensorflow/contrib/py2tf/utils/__init__.py b/tensorflow/contrib/py2tf/utils/__init__.py deleted file mode 100644 index 4e6003c852..0000000000 --- a/tensorflow/contrib/py2tf/utils/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2016 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Utility module that contains APIs usable in the generated code.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from tensorflow.contrib.py2tf.utils.builtins import dynamic_builtin -from tensorflow.contrib.py2tf.utils.builtins import dynamic_dataset -from tensorflow.contrib.py2tf.utils.builtins import dynamic_for_cond -from tensorflow.contrib.py2tf.utils.builtins import dynamic_print -from tensorflow.contrib.py2tf.utils.builtins import dynamic_range -from tensorflow.contrib.py2tf.utils.context_managers import control_dependency_on_returns -from tensorflow.contrib.py2tf.utils.misc import alias_tensors -from tensorflow.contrib.py2tf.utils.multiple_dispatch import dynamic_is -from tensorflow.contrib.py2tf.utils.multiple_dispatch import dynamic_is_not -from tensorflow.contrib.py2tf.utils.multiple_dispatch import run_cond -from tensorflow.contrib.py2tf.utils.multiple_dispatch import run_while -from tensorflow.contrib.py2tf.utils.py_func import wrap_py_func -from tensorflow.contrib.py2tf.utils.tensor_list import dynamic_list_append -from tensorflow.contrib.py2tf.utils.testing import fake_tf -from tensorflow.contrib.py2tf.utils.type_check import is_tensor -from tensorflow.contrib.py2tf.utils.type_hints import set_element_type diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index 8a80d6443b..e01306f953 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -164,12 +164,12 @@ sh_binary( "//tensorflow/contrib/lite/toco/python:toco_from_protos", "//tensorflow/contrib/nn:nn_py", "//tensorflow/contrib/predictor:predictor_pip", - "//tensorflow/contrib/py2tf:py2tf", - "//tensorflow/contrib/py2tf/converters:converters", - "//tensorflow/contrib/py2tf/converters:test_lib", - "//tensorflow/contrib/py2tf/impl:impl", - "//tensorflow/contrib/py2tf/pyct:pyct", - "//tensorflow/contrib/py2tf/pyct/static_analysis:static_analysis", + "//tensorflow/contrib/autograph:autograph", + "//tensorflow/contrib/autograph/converters:converters", + "//tensorflow/contrib/autograph/converters:test_lib", + "//tensorflow/contrib/autograph/impl:impl", + "//tensorflow/contrib/autograph/pyct:pyct", + "//tensorflow/contrib/autograph/pyct/static_analysis:static_analysis", "//tensorflow/contrib/receptive_field:receptive_field_pip", "//tensorflow/contrib/session_bundle:session_bundle_pip", "//tensorflow/contrib/signal:signal_py", -- GitLab From 710ba88846c9aca71ad1f83000255db4d3bb17e0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 13:41:35 -0700 Subject: [PATCH 228/906] Quick fix to assign_moving_average documentation formatting. PiperOrigin-RevId: 190517622 --- tensorflow/python/training/moving_averages.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/training/moving_averages.py b/tensorflow/python/training/moving_averages.py index b9ecb27df1..61fc828a84 100644 --- a/tensorflow/python/training/moving_averages.py +++ b/tensorflow/python/training/moving_averages.py @@ -52,16 +52,19 @@ def assign_moving_average(variable, value, decay, zero_debias=True, name=None): they were created in and the scope of the variables they debias. They are also given a uniqifying-suffix. - Ex: + E.g.: + + ``` with tf.variable_scope('scope1'): with tf.variable_scope('scope2'): var = tf.get_variable('foo') - assign_moving_average(var, 0.0, 1.0) - assign_moving_average(var, 0.0, 0.9) + tf.assign_moving_average(var, 0.0, 1.0) + tf.assign_moving_average(var, 0.0, 0.9) - var.name: 'scope1/scope2/foo' - shadow var names: 'scope1/scope2/scope1/scope2/foo/biased' - 'scope1/scope2/scope1/scope2/foo/biased_1' + # var.name: 'scope1/scope2/foo' + # shadow var names: 'scope1/scope2/scope1/scope2/foo/biased' + # 'scope1/scope2/scope1/scope2/foo/biased_1' + ``` Args: variable: A Variable. -- GitLab From 1fcef75aaa1989376324ff8dfc25033b443a69df Mon Sep 17 00:00:00 2001 From: Amit Patankar Date: Mon, 26 Mar 2018 13:48:00 -0700 Subject: [PATCH 229/906] Update BUILD --- tensorflow/contrib/timeseries/python/timeseries/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/timeseries/python/timeseries/BUILD b/tensorflow/contrib/timeseries/python/timeseries/BUILD index d72cc1b8a2..67ee644d3b 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/BUILD @@ -233,7 +233,7 @@ py_test( ], srcs_version = "PY2AND3", tags = [ - "manual", + "no_oss", "no_pip", # b/64527635 "no_pip_gpu", # b/63391119 ], -- GitLab From 72ed3c3b743e5feef99e37058dbd2f4344bcc5e3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 14:04:35 -0700 Subject: [PATCH 230/906] Add description of shapes and a pointer to external tutorial notebook in `tf.distributions.Distribution`. PiperOrigin-RevId: 190521666 --- .../python/ops/distributions/distribution.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tensorflow/python/ops/distributions/distribution.py b/tensorflow/python/ops/distributions/distribution.py index 0866fa8b0b..7c43bf54fc 100644 --- a/tensorflow/python/ops/distributions/distribution.py +++ b/tensorflow/python/ops/distributions/distribution.py @@ -338,6 +338,27 @@ class Distribution(_BaseDistribution): cum_prob_invalid = u.cdf([4.0, 5.0, 6.0]) ``` + #### Shapes + + There are three important concepts associated with TensorFlow Distributions + shapes: + - Event shape describes the shape of a single draw from the distribution; + it may be dependent across dimensions. For scalar distributions, the event + shape is `[]`. For a 5-dimensional MultivariateNormal, the event shape is + `[5]`. + - Batch shape describes independent, not identically distributed draws, aka a + "collection" or "bunch" of distributions. + - Sample shape describes independent, identically distributed draws of batches + from the distribution family. + + The event shape and the batch shape are properties of a Distribution object, + whereas the sample shape is associated with a specific call to `sample` or + `log_prob`. + + For detailed usage examples of TensorFlow Distributions shapes, see + [this tutorial]( + https://github.com/tensorflow/probability/blob/master/tensorflow_probability/examples/jupyter_notebooks/Understanding%20TensorFlow%20Distributions%20Shapes.ipynb) + #### Parameter values leading to undefined statistics or distributions. Some distributions do not have well-defined statistics for all initialization -- GitLab From 2ff8e913ad000d379405c284857e7fc81eef9fed Mon Sep 17 00:00:00 2001 From: Igor Ganichev Date: Mon, 26 Mar 2018 14:33:10 -0700 Subject: [PATCH 231/906] Clarify eager gradient doc strings PiperOrigin-RevId: 190526387 --- tensorflow/python/eager/backprop.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index a7837b8a7f..c54a5a1445 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -171,8 +171,8 @@ def implicit_val_and_grad(f): """Returns a function which differentiates f with respect to variables. The wrapped function returns the value and the gradient of f when called with - the same arguments. The gradient is with respect to all TFE variables which - are either trainable or have `variable.watch()` called on them by f. + the same arguments. The gradient is with respect to all trainable TFE + variables accessed by `f`. This function is useful when the exact set of variables to differentiate with is not known ahead of time. @@ -249,8 +249,8 @@ def implicit_grad(f): """Returns a function which differentiates f with respect to variables. The wrapped function returns the gradient of f when called with the same - arguments. The gradient is with respect to all TFE variables which are - either trainable or have `variable.watch()` called on them by f. + arguments. The gradient is with respect to all trainable TFE variables + accessed by `f`. This function is useful when the exact set of variables to differentiate with is not known ahead of time. -- GitLab From 0a86c23860968c66e95b9b6e930d14fac2699889 Mon Sep 17 00:00:00 2001 From: Jayaram Bobba Date: Mon, 26 Mar 2018 14:52:32 -0700 Subject: [PATCH 232/906] reverting mkl allocator inline modifier from #17396. causes build issues on linux systems (#18006) --- tensorflow/core/common_runtime/mkl_cpu_allocator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/common_runtime/mkl_cpu_allocator.h b/tensorflow/core/common_runtime/mkl_cpu_allocator.h index 73abf18d97..55c8411ad0 100644 --- a/tensorflow/core/common_runtime/mkl_cpu_allocator.h +++ b/tensorflow/core/common_runtime/mkl_cpu_allocator.h @@ -50,7 +50,7 @@ class MklCPUAllocator : public VisitableAllocator { // Constructor and other standard functions /// Environment variable that user can set to upper bound on memory allocation - static inline constexpr const char* kMaxLimitStr = "TF_MKL_ALLOC_MAX_BYTES"; + static constexpr const char* kMaxLimitStr = "TF_MKL_ALLOC_MAX_BYTES"; /// Default upper limit on allocator size - 64GB static constexpr size_t kDefaultMaxLimit = 64LL << 30; -- GitLab From 2c548819707bdafc8057cdd9c997f2a7b420d577 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 15:07:10 -0700 Subject: [PATCH 233/906] Fix some compiler warnings in MKL-DNN build. PiperOrigin-RevId: 190532168 --- tensorflow/core/graph/mkl_layout_pass.cc | 13 ++++++------- tensorflow/core/kernels/BUILD | 5 +++++ tensorflow/core/kernels/mkl_concat_op.cc | 6 ++++-- tensorflow/core/kernels/mkl_conv_ops.h | 9 ++++++--- tensorflow/core/kernels/mkl_fused_batch_norm_op.cc | 12 ++++++------ tensorflow/core/kernels/mkl_lrn_op.cc | 7 ++++--- tensorflow/core/kernels/mkl_reshape_op.cc | 3 ++- tensorflow/core/kernels/mkl_softmax_op.cc | 1 - tensorflow/core/util/mkl_util.h | 4 ++-- 9 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 1507b6eae2..5368774f2d 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -3103,8 +3103,7 @@ void MklLayoutRewritePass::GetDummyMklTensorNode(std::unique_ptr* g, TensorProto proto; proto.set_dtype(dt); uint8 zero[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - proto.set_tensor_content(const_cast(static_cast(&zero)), - 8); + proto.set_tensor_content(string(reinterpret_cast(&zero), 8)); TensorShape dummy_shape({8}); dummy_shape.AsProto(proto.mutable_tensor_shape()); TF_CHECK_OK(NodeBuilder((*g)->NewName("DMT"), "Const") @@ -3219,7 +3218,8 @@ int MklLayoutRewritePass::SetUpContiguousInputs( // For that let's first find filter node that is 2nd input (slot 1) // of BackpropInput. Node* filter_node = nullptr; - old_node->input_node(kConv2DBackpropInputFilterInputSlotIdx, &filter_node); + TF_CHECK_OK(old_node->input_node(kConv2DBackpropInputFilterInputSlotIdx, + &filter_node)); CHECK_NOTNULL(filter_node); // Now check which nodes receive from filter_node. Filter feeds as @@ -3399,8 +3399,7 @@ void MklLayoutRewritePass::GetDummyWorkspaceTensorNode( TensorProto proto; proto.set_dtype(dt); float zero[1] = {0}; - proto.set_tensor_content(const_cast(static_cast(&zero)), - 4); + proto.set_tensor_content(string(reinterpret_cast(&zero), 4)); TensorShape dummy_shape({1}); dummy_shape.AsProto(proto.mutable_tensor_shape()); TF_CHECK_OK(NodeBuilder((*g)->NewName("DMT"), "Const") @@ -3876,7 +3875,7 @@ Status MklLayoutRewritePass::MergeConv2DWithBiasAdd(std::unique_ptr* g, // Create node. Node* new_node; - nb.Finalize(&**g, &new_node); + TF_CHECK_OK(nb.Finalize(&**g, &new_node)); CHECK_NOTNULL(new_node); // Incoming data edges from 'pred' node and 'succ' node to new 'new_node' @@ -3987,7 +3986,7 @@ Status MklLayoutRewritePass::MergeConv2DBackpropFilterWithBiasAddGrad( // Create node. Node* new_node; - nb.Finalize(&**g, &new_node); + TF_CHECK_OK(nb.Finalize(&**g, &new_node)); CHECK_NOTNULL(new_node); // Incoming data edges from BiasAddGrad node and Conv2DBackpropFilter node to diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 8d235e79c0..9bb80eb892 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -5959,6 +5959,7 @@ tf_mkl_kernel_library( "//tensorflow/core:lib_internal", "//tensorflow/core:nn_ops_op_lib", "//third_party/mkl:intel_binary_blob", + "@mkl_dnn", ], ) @@ -5979,6 +5980,7 @@ tf_mkl_kernel_library( "//tensorflow/core:lib_internal", "//tensorflow/core:nn_ops_op_lib", "//third_party/mkl:intel_binary_blob", + "@mkl_dnn", ], ) @@ -6010,6 +6012,7 @@ tf_mkl_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:nn_ops_op_lib", + "//third_party/eigen3", "//third_party/mkl:intel_binary_blob", "@mkl_dnn", ], @@ -6029,6 +6032,7 @@ tf_mkl_kernel_library( prefix = "mkl_aggregate_ops", deps = MATH_DEPS + [ "//third_party/mkl:intel_binary_blob", + "@mkl_dnn", ], ) @@ -6046,6 +6050,7 @@ tf_mkl_kernel_library( prefix = "mkl_reshape_op", deps = ARRAY_DEPS + [ "//third_party/mkl:intel_binary_blob", + "@mkl_dnn", ], ) diff --git a/tensorflow/core/kernels/mkl_concat_op.cc b/tensorflow/core/kernels/mkl_concat_op.cc index aa3ea890b0..9ab95d765c 100644 --- a/tensorflow/core/kernels/mkl_concat_op.cc +++ b/tensorflow/core/kernels/mkl_concat_op.cc @@ -803,8 +803,10 @@ class MklConcatOp : public OpKernel { Tensor* output_tensor = nullptr; TensorShape tf_shape_output; tf_shape_output.AddDim(dnn_shape_output.GetSerializeBufferSize()); - context->allocate_output(GetTensorMetaDataIndex(0, context->num_outputs()), - tf_shape_output, &output_tensor); + OP_REQUIRES_OK(context, + context->allocate_output( + GetTensorMetaDataIndex(0, context->num_outputs()), + tf_shape_output, &output_tensor)); dnn_shape_output.SerializeMklDnnShape( output_tensor->flat().data(), output_tensor->flat().size() * sizeof(uint8)); diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index 7ca10db895..8333a09316 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -65,9 +65,12 @@ class MklDnnConvUtil { public: MklDnnConvUtil(OpKernelContext* context, const std::vector& strides, Padding pad, TensorFormat fm, - const std::vector& dilations) : - context_(context), strides_(strides), padding_(pad), - data_format_(fm), dilations_(dilations) {} + const std::vector& dilations) + : context_(context), + strides_(strides), + dilations_(dilations), + padding_(pad), + data_format_(fm) {} virtual ~MklDnnConvUtil() { context_ = nullptr; } diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index 9e564b016f..333a6570dc 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -817,8 +817,8 @@ class MklFusedBatchNormOp : public OpKernel { // set weights primitive // MKL-DNN packs scale & shift as "weights": // ...... - auto weights_desc = - memory::desc({2, depth_}, MklDnnType(), memory::format::nc); + auto weights_desc = memory::desc({2, static_cast(depth_)}, + MklDnnType(), memory::format::nc); auto weights_pd = memory::primitive_desc(weights_desc, cpu_engine); auto weights_m = memory(weights_pd); T* weights_data = reinterpret_cast(weights_m.get_data_handle()); @@ -833,8 +833,8 @@ class MklFusedBatchNormOp : public OpKernel { } // set mean primitive - auto mean_desc = - memory::desc({1, depth_}, MklDnnType(), memory::format::nc); + auto mean_desc = memory::desc({1, static_cast(depth_)}, + MklDnnType(), memory::format::nc); auto mean_pd = memory::primitive_desc(mean_desc, cpu_engine); char* saved_mean_data_tf = reinterpret_cast(saved_mean_tensor->flat().data()); @@ -844,8 +844,8 @@ class MklFusedBatchNormOp : public OpKernel { memory(mean_pd, reinterpret_cast(saved_mean_data_tf)); // set variance primitive - auto variance_desc = - memory::desc({1, depth_}, MklDnnType(), memory::format::nc); + auto variance_desc = memory::desc({1, static_cast(depth_)}, + MklDnnType(), memory::format::nc); auto variance_pd = memory::primitive_desc(variance_desc, cpu_engine); char* saved_variance_data_tf = reinterpret_cast(saved_variance_tensor->flat().data()); diff --git a/tensorflow/core/kernels/mkl_lrn_op.cc b/tensorflow/core/kernels/mkl_lrn_op.cc index 282012c719..eef254cdad 100644 --- a/tensorflow/core/kernels/mkl_lrn_op.cc +++ b/tensorflow/core/kernels/mkl_lrn_op.cc @@ -752,7 +752,8 @@ class MklLRNOp : public OpKernel { OP_REQUIRES_OK(context, context->GetAttr("alpha", &alpha_)); OP_REQUIRES_OK(context, context->GetAttr("beta", &beta_)); workspace_enabled_ = false; - context->GetAttr("workspace_enabled", &workspace_enabled_); + OP_REQUIRES_OK(context, + context->GetAttr("workspace_enabled", &workspace_enabled_)); } void Compute(OpKernelContext* context) override { @@ -1001,7 +1002,8 @@ class MklLRNGradOp : public OpKernel { OP_REQUIRES_OK(context, context->GetAttr("alpha", &alpha_)); OP_REQUIRES_OK(context, context->GetAttr("beta", &beta_)); workspace_enabled_ = false; - context->GetAttr("workspace_enabled", &workspace_enabled_); + OP_REQUIRES_OK(context, + context->GetAttr("workspace_enabled", &workspace_enabled_)); } void Compute(OpKernelContext* context) override { @@ -1043,7 +1045,6 @@ class MklLRNGradOp : public OpKernel { // Naming: diff_dst is input_gradient_tensor; src is orig_input_tensor. const Tensor& input_grad_tensor = MklGetInput(context, kIdxGradient); const Tensor& orig_input_tensor = MklGetInput(context, kIdxOrigInput); - const Tensor& orig_output_tensor = MklGetInput(context, kIdxOrigOutput); // Get input sizes in MKL-DNN required NCHW format. // LRN does not have data_format attribute. But by default it has diff --git a/tensorflow/core/kernels/mkl_reshape_op.cc b/tensorflow/core/kernels/mkl_reshape_op.cc index 5dbc4a2709..e12f6f437a 100644 --- a/tensorflow/core/kernels/mkl_reshape_op.cc +++ b/tensorflow/core/kernels/mkl_reshape_op.cc @@ -266,7 +266,8 @@ class MklReshapeOp : public OpKernel { &net)) { stream(stream::kind::eager).submit(net).wait(); } else { - output_tensor->CopyFrom(input_tensor, shape_to); + OP_REQUIRES(context, + output_tensor->CopyFrom(input_tensor, shape_to)); } return; } else { diff --git a/tensorflow/core/kernels/mkl_softmax_op.cc b/tensorflow/core/kernels/mkl_softmax_op.cc index aceef1e234..170523b5b4 100644 --- a/tensorflow/core/kernels/mkl_softmax_op.cc +++ b/tensorflow/core/kernels/mkl_softmax_op.cc @@ -27,7 +27,6 @@ limitations under the License. #include "mkldnn.h" #include "mkldnn_types.h" -#include "tensorflow/core/platform/default/logging.h" #include "tensorflow/core/util/mkl_util.h" #include "mkldnn.hpp" diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index 34db96075d..9f58e40d94 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -1579,10 +1579,10 @@ class MklDnnData { } /// Set function for data buffer of user memory primitive. - inline void* SetUsrMemDataHandle(void* data_buffer) { + inline void SetUsrMemDataHandle(void* data_buffer) { CHECK_NOTNULL(user_memory_); CHECK_NOTNULL(data_buffer); - return user_memory_->set_data_handle(data_buffer); + user_memory_->set_data_handle(data_buffer); } /// Set function for data buffer of user memory primitive. -- GitLab From 3a00d79b16348f0a53379e81b8e98bdd93d4833e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 15:19:29 -0700 Subject: [PATCH 234/906] [XLA] Redesign: implement and test unary and binary ops. Also, - Templatized ComputeAndCompareRX and CreateRXParameter so that they accept XlaBuilder and XlaOp. - Clear data held by an XlaBuilder when Build() is called, otherwise errors will occur when the builder is reused. PiperOrigin-RevId: 190534245 --- .../xla/client/xla_client/xla_builder.cc | 136 +++-- .../xla/client/xla_client/xla_builder.h | 3 + .../xla/client/xla_client/xla_builder_test.cc | 14 + .../compiler/xla/service/shape_inference.cc | 9 +- .../compiler/xla/service/shape_inference.h | 2 + tensorflow/compiler/xla/tests/BUILD | 1 + .../xla/tests/array_elementwise_ops_test.cc | 496 +++++++++--------- .../xla/tests/client_library_test_base.h | 123 ++--- 8 files changed, 430 insertions(+), 354 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index 596f39b4fd..bf91efcfd6 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -164,6 +164,11 @@ StatusOr XlaBuilder::Build() { } module->add_computations()->Swap(&entry); + // Clear data held by this builder. + this->instructions_.clear(); + this->embedded_.clear(); + this->parameter_numbers_.clear(); + return std::move(computation); } @@ -216,6 +221,16 @@ StatusOr XlaBuilder::AddBroadcastSequence(const Shape& output_shape, broadcast_dimensions); } +XlaOp XlaBuilder::UnaryOp(HloOpcode unop, const XlaOp& operand) { + return NoteErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + TF_ASSIGN_OR_RETURN(const Shape& operand_shape, operand.GetShape()); + TF_ASSIGN_OR_RETURN(*instr.mutable_shape(), + ShapeInference::InferUnaryOpShape(unop, operand_shape)); + return AddInstruction(std::move(instr), unop, {operand}); + }()); +} + XlaOp XlaBuilder::BinaryOp( HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { @@ -447,32 +462,32 @@ XlaOp XlaBuilder::GetTupleElement(const XlaOp& tuple_data, int64 index) { XlaOp XlaBuilder::Eq(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kEq, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Ne(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kNe, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Ge(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kGe, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Gt(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kGt, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Le(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kLe, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Lt(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kLt, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Dot(const XlaOp& lhs, const XlaOp& rhs) { @@ -551,102 +566,134 @@ XlaOp XlaBuilder::HostCompute(tensorflow::gtl::ArraySlice operands, XlaOp XlaBuilder::Complex( const XlaOp& real, const XlaOp& imag, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kComplex, real, imag, broadcast_dimensions); } XlaOp XlaBuilder::Conj(const XlaOp& operand) { return UnimplementedOp(); } XlaOp XlaBuilder::Sub(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kSubtract, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Div(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kDivide, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Rem(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kRemainder, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Max(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kMaximum, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Min(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kMinimum, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::And(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kAnd, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::Or(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kOr, lhs, rhs, broadcast_dimensions); } +// TODO(b/65209188): Create a dedicated lowering for Xor. XlaOp XlaBuilder::Xor(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return Or(And(Not(lhs), rhs, broadcast_dimensions), + And(lhs, Not(rhs), broadcast_dimensions)); } -XlaOp XlaBuilder::Not(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Not(const XlaOp& operand) { + return UnaryOp(HloOpcode::kNot, operand); +} XlaOp XlaBuilder::ShiftLeft( const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kShiftLeft, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::ShiftRightArithmetic( const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kShiftRightArithmetic, lhs, rhs, + broadcast_dimensions); } XlaOp XlaBuilder::ShiftRightLogical( const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kShiftRightLogical, lhs, rhs, + broadcast_dimensions); } -XlaOp XlaBuilder::Abs(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Abs(const XlaOp& operand) { + return UnaryOp(HloOpcode::kAbs, operand); +} XlaOp XlaBuilder::Atan2( const XlaOp& y, const XlaOp& x, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kAtan2, y, x, broadcast_dimensions); } -XlaOp XlaBuilder::Exp(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Exp(const XlaOp& operand) { + return UnaryOp(HloOpcode::kExp, operand); +} -XlaOp XlaBuilder::Floor(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Floor(const XlaOp& operand) { + return UnaryOp(HloOpcode::kFloor, operand); +} -XlaOp XlaBuilder::Ceil(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Ceil(const XlaOp& operand) { + return UnaryOp(HloOpcode::kCeil, operand); +} -XlaOp XlaBuilder::Round(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Round(const XlaOp& operand) { + return UnaryOp(HloOpcode::kRoundNearestAfz, operand); +} -XlaOp XlaBuilder::Log(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Log(const XlaOp& operand) { + return UnaryOp(HloOpcode::kLog, operand); +} -XlaOp XlaBuilder::Sign(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Sign(const XlaOp& operand) { + return UnaryOp(HloOpcode::kSign, operand); +} -XlaOp XlaBuilder::Cos(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Cos(const XlaOp& operand) { + return UnaryOp(HloOpcode::kCos, operand); +} -XlaOp XlaBuilder::Sin(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Sin(const XlaOp& operand) { + return UnaryOp(HloOpcode::kSin, operand); +} -XlaOp XlaBuilder::Tanh(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Tanh(const XlaOp& operand) { + return UnaryOp(HloOpcode::kTanh, operand); +} -XlaOp XlaBuilder::Real(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Real(const XlaOp& operand) { + return UnaryOp(HloOpcode::kReal, operand); +} -XlaOp XlaBuilder::Imag(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Imag(const XlaOp& operand) { + return UnaryOp(HloOpcode::kImag, operand); +} -XlaOp XlaBuilder::IsFinite(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::IsFinite(const XlaOp& operand) { + return UnaryOp(HloOpcode::kIsFinite, operand); +} XlaOp XlaBuilder::Transpose(const XlaOp& operand, tensorflow::gtl::ArraySlice permutation) { @@ -668,13 +715,18 @@ XlaOp XlaBuilder::Rev(const XlaOp& operand, return UnimplementedOp(); } -XlaOp XlaBuilder::Sort(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Sort(const XlaOp& operand) { + return UnaryOp(HloOpcode::kSort, operand); +} -XlaOp XlaBuilder::SqrtF32(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::SqrtF32(const XlaOp& operand) { + return BinaryOp(HloOpcode::kPower, operand, ConstantR0(0.5), + /*broadcast_dimensions=*/{}); +} XlaOp XlaBuilder::Pow(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kPower, lhs, rhs, broadcast_dimensions); } XlaOp XlaBuilder::ConvertElementType(const XlaOp& operand, @@ -687,13 +739,19 @@ XlaOp XlaBuilder::BitcastConvertType(const XlaOp& operand, return UnimplementedOp(); } -XlaOp XlaBuilder::SquareF32(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::SquareF32(const XlaOp& operand) { + return BinaryOp(HloOpcode::kPower, operand, ConstantR0(2.0), + /*broadcast_dimensions=*/{}); +} XlaOp XlaBuilder::ReciprocalF32(const XlaOp& operand) { - return UnimplementedOp(); + return BinaryOp(HloOpcode::kPower, operand, ConstantR0(-1.0), + /*broadcast_dimensions=*/{}); } -XlaOp XlaBuilder::Neg(const XlaOp& operand) { return UnimplementedOp(); } +XlaOp XlaBuilder::Neg(const XlaOp& operand) { + return UnaryOp(HloOpcode::kNegate, operand); +} XlaOp XlaBuilder::Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max) { diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index c19eb47165..22cf094512 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -730,6 +730,9 @@ class XlaBuilder { StatusOr LookUpInstruction(const XlaOp& op) const; + // Internal helper method that does the building for an arbitrary unary op. + XlaOp UnaryOp(HloOpcode unop, const XlaOp& operand); + // Internal helper method that does the building for an arbitrary binary op. // broadcast_dimensions specifies which dimensions to use for broadcasting // when the operation is between tensors of different ranks. diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc index 529287a57a..85d4227ba4 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc @@ -217,5 +217,19 @@ TEST_F(XlaBuilderTest, Transpose) { EXPECT_THAT(root, op::Transpose(op::Parameter())); } +// TODO(b/65209188): Create a dedicated lowering for Xor. +TEST_F(XlaBuilderTest, Xor) { + XlaBuilder b(TestName()); + auto x = b.Parameter(0, ShapeUtil::MakeShape(PRED, {}), "x"); + auto y = b.Parameter(1, ShapeUtil::MakeShape(PRED, {}), "y"); + b.Xor(x, y); + TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); + auto root = module->entry_computation()->root_instruction(); + LOG(ERROR) << module->ToString(); + EXPECT_THAT(root, + op::Or(op::And(op::Not(op::Parameter(0)), op::Parameter(1)), + op::And(op::Parameter(0), op::Not(op::Parameter(1))))); +} + } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index 8c8bd6d73a..2a70ea0354 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -304,12 +304,17 @@ StatusOr InferWindowOutputShape(const Shape& base_shape, /* static */ StatusOr ShapeInference::InferUnaryOpShape( HloOpcode opcode, const HloInstruction* operand) { + return InferUnaryOpShape(opcode, operand->shape()); +} + +/* static */ StatusOr ShapeInference::InferUnaryOpShape( + HloOpcode opcode, const Shape& shape) { // There is no copy operation at the proto level, so handle copy explicitly. if (opcode == HloOpcode::kCopy) { - return operand->shape(); + return shape; } - return InferUnaryOpShape(OpcodeToUnaryOperation(opcode), operand->shape()); + return InferUnaryOpShape(OpcodeToUnaryOperation(opcode), shape); } /* static */ StatusOr ShapeInference::InferUnaryOpShape( diff --git a/tensorflow/compiler/xla/service/shape_inference.h b/tensorflow/compiler/xla/service/shape_inference.h index 085fdac60c..b6552a34ae 100644 --- a/tensorflow/compiler/xla/service/shape_inference.h +++ b/tensorflow/compiler/xla/service/shape_inference.h @@ -48,6 +48,8 @@ class ShapeInference { // given input shape. static StatusOr InferUnaryOpShape(UnaryOperation operation, const Shape& arg); + static StatusOr InferUnaryOpShape(HloOpcode opcode, + const Shape& shape); static StatusOr InferUnaryOpShape(HloOpcode opcode, const HloInstruction* operand); diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 26022278e5..3705d6c271 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -598,6 +598,7 @@ xla_test( "//tensorflow/compiler/xla/client:computation_builder", "//tensorflow/compiler/xla/client:global_data", "//tensorflow/compiler/xla/client:local_client", + "//tensorflow/compiler/xla/client/xla_client:xla_builder", "//tensorflow/compiler/xla/tests:client_library_test_base", "//tensorflow/compiler/xla/tests:literal_test_util", "//tensorflow/compiler/xla/tests:xla_internal_test_main", diff --git a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc index 6e21dda25d..fa7ac3ca9b 100644 --- a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc +++ b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/client/global_data.h" #include "tensorflow/compiler/xla/client/local_client.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" #include "tensorflow/compiler/xla/layout_util.h" #include "tensorflow/compiler/xla/literal_util.h" #include "tensorflow/compiler/xla/statusor.h" @@ -50,28 +51,28 @@ class ArrayElementwiseOpTestParamCount public ::testing::WithParamInterface {}; XLA_TEST_F(ArrayElementwiseOpTest, NegConstantZeroElementF32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto result = builder.Neg(a); + builder.Neg(a); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, NegConstantF32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 3.14f, 2.25f, -10.0f, 6.0f}); - auto result = builder.Neg(a); + builder.Neg(a); ComputeAndCompareR1(&builder, {2.5f, -3.14f, -2.25f, 10.0f, -6.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, NegConstantS32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-1, 0, 1, 324, std::numeric_limits::min(), std::numeric_limits::max()}); - auto result = builder.Neg(a); + builder.Neg(a); // -min == min for int32 due to an overflow. In C++ it is undefined behavior // to do this calculation. For XLA we have not specified that, so it @@ -83,18 +84,18 @@ XLA_TEST_F(ArrayElementwiseOpTest, NegConstantS32) { } XLA_TEST_F(ArrayElementwiseOpTest, NegConstantZeroElementC64) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto result = builder.Neg(a); + builder.Neg(a); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, NegConstantC64) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {{-2.5f, 1.0f}, {0.0f, 3.14f}, {2.25f, -1.0f}, {-10.0f, 0.0f}}); - auto result = builder.Neg(a); + builder.Neg(a); ComputeAndCompareR1( &builder, {{2.5f, -1.0f}, {0.0f, -3.14f}, {-2.25f, 1.0f}, {10.0f, 0.0f}}, @@ -102,7 +103,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, NegConstantC64) { } XLA_TEST_F(ArrayElementwiseOpTest, NegConstantS64) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({ -1, 1, @@ -112,7 +113,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, NegConstantS64) { static_cast(0x8000000000000000LL), static_cast(0x8000000000000001LL), }); - auto result = builder.Neg(a); + builder.Neg(a); LOG(INFO) << -static_cast(0x7FFFFFFFFFFFFFFFLL); ComputeAndCompareR1(&builder, @@ -129,9 +130,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, NegConstantS64) { } XLA_TEST_F(ArrayElementwiseOpTest, IsFiniteZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto result = builder.IsFinite(a); + builder.IsFinite(a); ComputeAndCompareR1(&builder, {}, {}); } @@ -140,64 +141,63 @@ XLA_TEST_F(ArrayElementwiseOpTest, IsFiniteZeroElementF32s) { static const float kNonCanonicalNaN = tensorflow::bit_cast(0x7FD01234); XLA_TEST_F(ArrayElementwiseOpTest, IsFiniteScalarF32) { - ComputationBuilder builder(client_, TestName()); - auto result = builder.IsFinite(builder.ConstantR0(NAN)); + XlaBuilder builder(TestName()); + builder.IsFinite(builder.ConstantR0(NAN)); ComputeAndCompareR0(&builder, false, {}); EXPECT_TRUE(std::isnan(kNonCanonicalNaN)); - auto result_non_canonical = - builder.IsFinite(builder.ConstantR0(kNonCanonicalNaN)); + builder.IsFinite(builder.ConstantR0(kNonCanonicalNaN)); ComputeAndCompareR0(&builder, false, {}); const float inf = std::numeric_limits::infinity(); - auto result_inf = builder.IsFinite(builder.ConstantR0(inf)); + builder.IsFinite(builder.ConstantR0(inf)); ComputeAndCompareR0(&builder, false, {}); - auto result_neg_inf = builder.IsFinite(builder.ConstantR0(-inf)); + builder.IsFinite(builder.ConstantR0(-inf)); ComputeAndCompareR0(&builder, false, {}); - auto result_zero = builder.IsFinite(builder.ConstantR0(0.0f)); + builder.IsFinite(builder.ConstantR0(0.0f)); ComputeAndCompareR0(&builder, true, {}); } XLA_TEST_F(ArrayElementwiseOpTest, IsFiniteR1F32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); const float inf = std::numeric_limits::infinity(); EXPECT_TRUE(std::isnan(kNonCanonicalNaN)); auto a = builder.ConstantR1( {{NAN, 7.0f, kNonCanonicalNaN, -1.0f, inf, -inf}}); - auto result = builder.IsFinite(a); + builder.IsFinite(a); ComputeAndCompareR1(&builder, {false, true, false, true, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 3.14f, 2.25f, -10.0f, 6.0f}); auto b = builder.ConstantR1({100.0f, 3.13f, 2.75f, 10.5f, -999.0f}); - auto add = builder.Add(a, b); + builder.Add(a, b); ComputeAndCompareR1(&builder, {97.5f, 6.27f, 5.0f, 0.5f, -993.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Add(a, b); + builder.Add(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {{-2.5f, 0.0f}, {0.0f, 3.14f}, {2.25f, 0.0f}, {1.0f, -10.0f}}); auto b = builder.ConstantR1( {{100.0f, 0.0f}, {3.13f, 0.0f}, {2.75f, 1.0f}, {-2.0f, 10.5f}}); - auto add = builder.Add(a, b); + builder.Add(a, b); ComputeAndCompareR1( &builder, {97.5f, {3.13f, 3.14f}, {5.0f, 1.0f}, {-1.0f, 0.5f}}, {}, @@ -205,10 +205,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantC64s) { } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantZeroElementC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Add(a, b); + builder.Add(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } @@ -295,7 +295,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantS64s) { TEST_P(ArrayElementwiseOpTestParamCount, AddManyValues) { const int count = GetParam(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::vector a_values; std::vector b_values; for (int i = 0; i < count; ++i) { @@ -334,49 +334,49 @@ TEST_P(ArrayElementwiseOpTestParamCount, AddManyValues) { } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 3.14f, 2.25f, -10.0f, 6.0f}); auto b = builder.ConstantR1({100.0f, 3.13f, 2.75f, 10.5f, -999.0f}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1(&builder, {-102.5f, 0.01f, -0.5f, -20.5f, 1005.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-1, 0, 2, 1000000000}); auto b = builder.ConstantR1({-1, 2, 1, -1}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1(&builder, {0, -2, 1, 1000000001}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantZeroElementS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {{-2.5f, 0.0f}, {0.0f, 3.14f}, {3.0f, 2.25f}}); auto b = builder.ConstantR1( {{0.0f, 10.0f}, {3.13f, 0.0f}, {2.75f, -0.25f}}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1( &builder, {{-2.5f, -10.0f}, {-3.13f, 3.14f}, {0.25f, 2.5f}}, {}, @@ -384,29 +384,29 @@ XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantC64s) { } XLA_TEST_F(ArrayElementwiseOpTest, SubTwoConstantZeroElementC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Sub(a, b); + builder.Sub(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, DivTwoConstantF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 25.5f, 2.25f, -10.0f, 6.0f}); auto b = builder.ConstantR1({10.0f, 5.1f, 1.0f, 10.0f, -6.0f}); - auto add = builder.Div(a, b); + builder.Div(a, b); ComputeAndCompareR1(&builder, {-0.25f, 5.0f, 2.25f, -1.0f, -1.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, DivTwoConstantZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Div(a, b); + builder.Div(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } @@ -436,9 +436,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivS32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; - ComputationDataHandle divisor; + XlaBuilder builder(TestName()); + XlaOp dividend; + XlaOp divisor; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); auto divisor_data = @@ -451,8 +451,8 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivS32s) { // Test with a compile-time constant divisor. { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; + XlaBuilder builder(TestName()); + XlaOp dividend; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); builder.Div(dividend, builder.ConstantR1(divisors)); @@ -461,9 +461,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivS32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; - ComputationDataHandle divisor; + XlaBuilder builder(TestName()); + XlaOp dividend; + XlaOp divisor; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); auto divisor_data = @@ -476,8 +476,8 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivS32s) { // Test with a compile-time constant divisor. { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; + XlaBuilder builder(TestName()); + XlaOp dividend; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); builder.Rem(dividend, builder.ConstantR1(divisors)); @@ -507,9 +507,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivU32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; - ComputationDataHandle divisor; + XlaBuilder builder(TestName()); + XlaOp dividend; + XlaOp divisor; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); auto divisor_data = @@ -521,8 +521,8 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivU32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; + XlaBuilder builder(TestName()); + XlaOp dividend; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); builder.Div(dividend, builder.ConstantR1(divisors)); @@ -531,9 +531,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivU32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; - ComputationDataHandle divisor; + XlaBuilder builder(TestName()); + XlaOp dividend; + XlaOp divisor; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); auto divisor_data = @@ -545,8 +545,8 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivU32s) { } { - ComputationBuilder builder(client_, TestName()); - ComputationDataHandle dividend; + XlaBuilder builder(TestName()); + XlaOp dividend; auto dividend_data = CreateR1Parameter(dividends, 0, "dividend", &builder, ÷nd); builder.Rem(dividend, builder.ConstantR1(divisors)); @@ -556,33 +556,33 @@ XLA_TEST_F(ArrayElementwiseOpTest, DivU32s) { } XLA_TEST_F(ArrayElementwiseOpTest, DivTwoConstantC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {{-2.5f, 1.0f}, {-25.5f, 0.0f}, {2.0f, -1.0f}}); auto b = builder.ConstantR1( {{10.0f, 0.0f}, {0.0f, 1.0f}, {2.0f, -1.0f}}); - auto div = builder.Div(a, b); + builder.Div(a, b); ComputeAndCompareR1( &builder, {{-0.25f, 0.1f}, {0.0f, 25.5f}, {1.0f, 0.0f}}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, DivTwoConstantZeroElementC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto div = builder.Div(a, b); + builder.Div(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, RemF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {-2.5f, 25.5f, 2.25f, -10.0f, 6.0f, 3.0f, 3.0f, -1.0f, -8.0f}); auto b = builder.ConstantR1( {10.0f, 5.1f, 1.0f, 10.0f, -6.0f, 2.0f, -2.0f, 7.0f, -4.0f}); - auto add = builder.Rem(a, b); + builder.Rem(a, b); ComputeAndCompareR1( &builder, {-2.5f, 0.0f, 0.25f, 0.0f, -0.0f, 1.0f, 1.0f, -1.0f, -0.0f}, {}, @@ -590,21 +590,21 @@ XLA_TEST_F(ArrayElementwiseOpTest, RemF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, RemZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Rem(a, b); + builder.Rem(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, RemF64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {-2.5, 25.5, 2.25, -10.0, 6.0, 3.0, 3.0, -1.0, -8.0}); auto b = builder.ConstantR1( {10.0, 5.1, 1.0, 10.0, -6.0, 2.0, -2.0, 7.0, -4.0}); - auto add = builder.Rem(a, b); + builder.Rem(a, b); ComputeAndCompareR1( &builder, {-2.5, 0.0, 0.25, 0.0, -0.0, 1.0, 1.0, -1.0, -0.0}, {}, @@ -612,20 +612,20 @@ XLA_TEST_F(ArrayElementwiseOpTest, RemF64s) { } XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 25.5f, 2.25f, -10.0f, 6.0f}); auto b = builder.ConstantR1({10.0f, 5.0f, 1.0f, 10.0f, -6.0f}); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, {-25.0f, 127.5f, 2.25f, -100.0f, -36.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } @@ -648,19 +648,19 @@ XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantS32s) { } } - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1(a_data); auto b = builder.ConstantR1(b_data); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, expected, {}); } XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantZeroElementS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, {}, {}); } @@ -679,21 +679,21 @@ XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantU32s) { } } - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1(a_data); auto b = builder.ConstantR1(b_data); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, expected, {}); } XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {{-2.5f, 0.0f}, {0.0f, 25.5f}, {2.0f, -10.0f}}); auto b = builder.ConstantR1( {{0.0f, 10.0f}, {5.0f, 1.0f}, {10.0f, -6.0f}}); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1( &builder, {{0.0f, -25.0f}, {-25.5f, 127.5f}, {-40.0f, -112.0}}, {}, @@ -701,264 +701,264 @@ XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantC64s) { } XLA_TEST_F(ArrayElementwiseOpTest, MulTwoConstantZeroElementC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto add = builder.Mul(a, b); + builder.Mul(a, b); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, AndPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({false, false, true, true}); auto b = builder.ConstantR1({false, true, false, true}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {false, false, false, true}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndPredR2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{false, false}, {true, true}}); auto b = builder.ConstantR2({{false, true}, {false, true}}); - auto out = builder.And(a, b); + builder.And(a, b); Array2D expected_array({{false, false}, {false, true}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndZeroElementPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0, -1, -8}); auto b = builder.ConstantR1({5, -7, 12}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {0, -7, 8}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndS32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{0, -5}, {-1, 5}}); auto b = builder.ConstantR2({{1, -6}, {4, 5}}); - auto out = builder.And(a, b); + builder.And(a, b); Array2D expected_array({{0, -6}, {4, 5}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndZeroElementS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0, 1, 8}); auto b = builder.ConstantR1({5, 7, 12}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {0, 1, 8}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndU32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{0, 1}, {3, 8}}); auto b = builder.ConstantR2({{1, 0}, {7, 6}}); - auto out = builder.And(a, b); + builder.And(a, b); Array2D expected_array({{0, 0}, {3, 0}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AndZeroElementU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.And(a, b); + builder.And(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({false, false, true, true}); auto b = builder.ConstantR1({false, true, false, true}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {false, true, true, true}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrPredR2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{false, false}, {true, true}}); auto b = builder.ConstantR2({{false, true}, {false, true}}); - auto out = builder.Or(a, b); + builder.Or(a, b); Array2D expected_array({{false, true}, {true, true}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrZeroElementPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0, -1, 8}); auto b = builder.ConstantR1({5, -7, 4}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {5, -1, 12}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrS32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{0, -1}, {8, 8}}); auto b = builder.ConstantR2({{5, -7}, {4, 1}}); - auto out = builder.Or(a, b); + builder.Or(a, b); Array2D expected_array({{5, -1}, {12, 9}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrZeroElementS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0, 1, 8}); auto b = builder.ConstantR1({5, 7, 4}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {5, 7, 12}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrU32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{0, 1}, {8, 8}}); auto b = builder.ConstantR2({{5, 7}, {4, 1}}); - auto out = builder.Or(a, b); + builder.Or(a, b); Array2D expected_array({{5, 7}, {12, 9}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, OrZeroElementU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); auto b = builder.ConstantR1({}); - auto out = builder.Or(a, b); + builder.Or(a, b); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({false, true, true, false}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {true, false, false, true}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotPredR2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{false, true}, {true, false}}); - auto out = builder.Not(a); + builder.Not(a); Array2D expected_array({{true, false}, {false, true}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotZeroElementPredR1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-1, 0, 1}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {0, -1, -2}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotS32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-1, 0}, {1, 8}}); - auto out = builder.Not(a); + builder.Not(a); Array2D expected_array({{0, -1}, {-2, -9}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotZeroElementS32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0, 4294967295}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {4294967295, 0}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotU32R2) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{0, 4294967295}, {1, 4294967294}}); - auto out = builder.Not(a); + builder.Not(a); Array2D expected_array({{4294967295, 0}, {4294967294, 1}}); ComputeAndCompareR2(&builder, expected_array, {}); } XLA_TEST_F(ArrayElementwiseOpTest, NotZeroElementU32R1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({}); - auto out = builder.Not(a); + builder.Not(a); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ShiftLeftS32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({static_cast(0x12345678), static_cast(0xF0001000), 1, 3, 77, 1, -3, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 15, 32, 100, -1}); - auto out = builder.ShiftLeft(a, b); + builder.ShiftLeft(a, b); ComputeAndCompareR1(&builder, {static_cast(0x23456780), 0x00100000, 0x4, @@ -967,12 +967,12 @@ XLA_TEST_F(ArrayElementwiseOpTest, ShiftLeftS32) { } XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightArithmeticS32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({static_cast(0x92345678), static_cast(0x10001000), 1, 3, 77, 1, -3, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 2, 32, 100, -1}); - auto out = builder.ShiftRightArithmetic(a, b); + builder.ShiftRightArithmetic(a, b); ComputeAndCompareR1( &builder, @@ -982,45 +982,45 @@ XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightArithmeticS32) { } XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightLogicalS32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({static_cast(0x92345678), static_cast(0x10001000), 1, 3, 77, 1, -3, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 5, 32, 100, -1}); - auto out = builder.ShiftRightLogical(a, b); + builder.ShiftRightLogical(a, b); ComputeAndCompareR1(&builder, {0x09234567, 0x00100010, 0, 0, 2, 0, 0, 0}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ShiftLeftU32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {0x12345678, 0xF0001000, 1, 3, 77, 1, ~3u, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 15, 32, 100, ~0u}); - auto out = builder.ShiftLeft(a, b); + builder.ShiftLeft(a, b); ComputeAndCompareR1( &builder, {0x23456780, 0x00100000, 0x4, 0x180, 2523136, 0, 0, 0}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightArithmeticU32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {0x92345678, 0x10001000, 1, 3, 77, 1, ~3u, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 2, 32, 100, ~0u}); - auto out = builder.ShiftRightArithmetic(a, b); + builder.ShiftRightArithmetic(a, b); ComputeAndCompareR1( &builder, {0xF9234567, 0x00100010, 0, 0, 19, 0, ~0u, 0}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightLogicalU32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1( {0x92345678, 0x10001000, 1, 3, 77, 1, ~3u, 77}); auto b = builder.ConstantR1({4, 8, 2, 7, 5, 32, 100, ~0u}); - auto out = builder.ShiftRightLogical(a, b); + builder.ShiftRightLogical(a, b); ComputeAndCompareR1(&builder, {0x09234567, 0x00100010, 0, 0, 2, 0, 0, 0}, {}); @@ -1028,59 +1028,59 @@ XLA_TEST_F(ArrayElementwiseOpTest, ShiftRightLogicalU32) { XLA_TEST_F(ArrayElementwiseOpTest, CompareEqF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 25.5f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 5.0f, 2.25f, 10.0f, NAN}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1(&builder, {false, false, true, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareEqZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareGeF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 25.5f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 5.0f, 1.0f, 10.0f, NAN}); - auto compare = builder.Ge(lhs, rhs); + builder.Ge(lhs, rhs); ComputeAndCompareR1(&builder, {false, true, true, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareGtF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 25.5f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 5.0f, 1.0f, 10.0f, NAN}); - auto compare = builder.Gt(lhs, rhs); + builder.Gt(lhs, rhs); ComputeAndCompareR1(&builder, {false, true, true, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareLeF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 5.0f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 5.0f, 1.0f, 10.0f, NAN}); - auto compare = builder.Le(lhs, rhs); + builder.Le(lhs, rhs); ComputeAndCompareR1(&builder, {true, true, false, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareLtF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 25.5f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 5.0f, 1.0f, 10.0f, NAN}); - auto compare = builder.Lt(lhs, rhs); + builder.Lt(lhs, rhs); ComputeAndCompareR1(&builder, {true, false, false, false, false}, {}); } @@ -1088,10 +1088,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareLtF32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareEqS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1( &builder, {true, false, false, false, true, false, false, false, true}, @@ -1099,17 +1099,17 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareEqS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, CompareEqZeroElementS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareEqC64s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({{-2.5f, 10.0f}, {1.0f, 25.5f}, {2.25f, -3.0f}, @@ -1120,16 +1120,16 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareEqC64s) { {2.25f, -3.0f}, {10.0f, 0.0f}, {1.0f, NAN}}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1(&builder, {false, false, true, false, false}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, CompareEqZeroElementC64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}); } @@ -1138,7 +1138,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeC64s) { // Disable fast-math because we're operating on NaNs. SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({{-2.5f, 10.0f}, {1.0f, 25.5f}, {2.25f, -3.0f}, @@ -1149,7 +1149,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeC64s) { {2.25f, -3.0f}, {10.0f, 0.0f}, {1.0f, NAN}}); - auto compare = builder.Ne(lhs, rhs); + builder.Ne(lhs, rhs); ComputeAndCompareR1(&builder, {true, true, false, true, true}, {}); } @@ -1158,10 +1158,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeF32s) { // Disable fast-math because we're operating on NaNs. SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.5f, 25.5f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({10.0f, 25.5f, 1.0f, 10.0f, NAN}); - auto compare = builder.Ne(lhs, rhs); + builder.Ne(lhs, rhs); ComputeAndCompareR1(&builder, {true, false, true, true, true}, {}); } @@ -1169,10 +1169,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeF32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareNeS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Ne(lhs, rhs); + builder.Ne(lhs, rhs); ComputeAndCompareR1( &builder, {false, true, true, true, false, true, true, true, false}, {}); @@ -1181,10 +1181,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeS32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareGeS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Ge(lhs, rhs); + builder.Ge(lhs, rhs); ComputeAndCompareR1( &builder, {true, false, false, true, true, false, true, true, true}, {}); @@ -1193,10 +1193,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGeS32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareGtS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Gt(lhs, rhs); + builder.Gt(lhs, rhs); ComputeAndCompareR1( &builder, {false, false, false, true, false, false, true, true, false}, @@ -1206,10 +1206,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGtS32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareLeS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Le(lhs, rhs); + builder.Le(lhs, rhs); ComputeAndCompareR1( &builder, {true, true, true, false, true, true, false, false, true}, {}); @@ -1218,10 +1218,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareLeS32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareLtS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({min, min, min, 0, 0, 0, max, max, max}); auto rhs = builder.ConstantR1({min, 0, max, -1, 0, 1, min, 0, max}); - auto compare = builder.Lt(lhs, rhs); + builder.Lt(lhs, rhs); ComputeAndCompareR1( &builder, {false, true, true, false, false, true, false, false, false}, @@ -1230,10 +1230,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareLtS32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareEqU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Eq(lhs, rhs); + builder.Eq(lhs, rhs); ComputeAndCompareR1( &builder, {true, false, false, false, true, false, false, false, true}, @@ -1242,10 +1242,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareEqU32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareNeU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Ne(lhs, rhs); + builder.Ne(lhs, rhs); ComputeAndCompareR1( &builder, {false, true, true, true, false, true, true, true, false}, {}); @@ -1253,10 +1253,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareNeU32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareGeU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Ge(lhs, rhs); + builder.Ge(lhs, rhs); ComputeAndCompareR1( &builder, {true, false, false, true, true, false, true, true, true}, {}); @@ -1264,10 +1264,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGeU32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareGtU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Gt(lhs, rhs); + builder.Gt(lhs, rhs); ComputeAndCompareR1( &builder, {false, false, false, true, false, false, true, true, false}, @@ -1276,10 +1276,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGtU32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareLeU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Le(lhs, rhs); + builder.Le(lhs, rhs); ComputeAndCompareR1( &builder, {true, true, true, false, true, true, false, false, true}, {}); @@ -1287,10 +1287,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareLeU32s) { XLA_TEST_F(ArrayElementwiseOpTest, CompareLtU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({0, 0, 0, 5, 5, 5, max, max, max}); auto rhs = builder.ConstantR1({0, 1, max, 4, 5, 6, 0, 1, max}); - auto compare = builder.Lt(lhs, rhs); + builder.Lt(lhs, rhs); ComputeAndCompareR1( &builder, {false, true, true, false, false, true, false, false, false}, @@ -1299,12 +1299,12 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareLtU32s) { XLA_TEST_F(ArrayElementwiseOpTest, PowF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({4.0f, 2.0f, 2.0f, NAN, 6.0f, -2.0f, -2.0f}); auto rhs = builder.ConstantR1({2.0f, -2.0f, 3.0f, 10.0f, NAN, 3.0f, 4.0f}); - auto minimum = builder.Pow(lhs, rhs); + builder.Pow(lhs, rhs); ComputeAndCompareR1( &builder, {16.0f, 0.25f, 8.0f, NAN, NAN, -8.0f, 16.0f}, {}, error_spec_); @@ -1312,20 +1312,20 @@ XLA_TEST_F(ArrayElementwiseOpTest, PowF32s) { XLA_TEST_F(ArrayElementwiseOpTest, PowNonIntegerF32s) { SetFastMathDisabled(true); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({-2.0f, -0.6f, -0.6f, 0.0f}); auto rhs = builder.ConstantR1({0.5f, 0.6f, -0.6f, -0.6f}); - auto minimum = builder.Pow(lhs, rhs); + builder.Pow(lhs, rhs); ComputeAndCompareR1(&builder, {NAN, NAN, NAN, INFINITY}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, PowZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto minimum = builder.Pow(lhs, rhs); + builder.Pow(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } @@ -1599,14 +1599,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, Div4F32) { TEST_P(ArrayElementwiseOpTestParamCount, SquareManyValues) { const int count = GetParam(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::vector values; values.reserve(count); for (int i = 0; i < count; ++i) { values.push_back(i / static_cast(count)); } auto x = builder.ConstantR1(values); - auto exp = builder.Pow(x, builder.ConstantR0(2.0f)); + builder.Pow(x, builder.ConstantR0(2.0f)); std::vector expected; expected.reserve(values.size()); @@ -1618,7 +1618,7 @@ TEST_P(ArrayElementwiseOpTestParamCount, SquareManyValues) { } XLA_TEST_F(ArrayElementwiseOpTest, SquareIn4D) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); Array4D values(2, 2, 2, 2); std::vector values_vector; @@ -1632,77 +1632,77 @@ XLA_TEST_F(ArrayElementwiseOpTest, SquareIn4D) { Array4D expected(2, 2, 2, 2, expected_vector); auto x = builder.ConstantR4FromArray4D(values); - auto exp = builder.Pow(x, builder.ConstantR0(2.0f)); + builder.Pow(x, builder.ConstantR0(2.0f)); ComputeAndCompareR4(&builder, expected, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, SquareIn4DZeroElements) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); Array4D values(2, 2, 0, 2); Array4D expected(2, 2, 0, 2); auto x = builder.ConstantR4FromArray4D(values); - auto exp = builder.Pow(x, builder.ConstantR0(2.0f)); + builder.Pow(x, builder.ConstantR0(2.0f)); ComputeAndCompareR4(&builder, expected, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MinF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); SetFastMathDisabled(true); auto lhs = builder.ConstantR1({1.0f, 1.0f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({2.0f, -5.0f, 1.0f, 10.0f, NAN}); - auto minimum = builder.Min(lhs, rhs); + builder.Min(lhs, rhs); ComputeAndCompareR1(&builder, {1.0f, -5.0f, 1.0f, NAN, NAN}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MinZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto minimum = builder.Min(lhs, rhs); + builder.Min(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MinF64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); SetFastMathDisabled(true); auto lhs = builder.ConstantR1({1.0, 1.0, 2.25, NAN, 6.0}); auto rhs = builder.ConstantR1({2.0, -5.0, 1.0, 10.0, NAN}); - auto minimum = builder.Min(lhs, rhs); + builder.Min(lhs, rhs); ComputeAndCompareR1(&builder, {1.0, -5.0, 1.0, NAN, NAN}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MaxF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); SetFastMathDisabled(true); auto lhs = builder.ConstantR1({1.0f, 1.0f, 2.25f, NAN, 6.0f}); auto rhs = builder.ConstantR1({2.0f, -5.0f, 1.0f, 10.0f, NAN}); - auto maximum = builder.Max(lhs, rhs); + builder.Max(lhs, rhs); ComputeAndCompareR1(&builder, {2.0f, 1.0f, 2.25f, NAN, NAN}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MaxZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto lhs = builder.ConstantR1({}); auto rhs = builder.ConstantR1({}); - auto minimum = builder.Max(lhs, rhs); + builder.Max(lhs, rhs); ComputeAndCompareR1(&builder, {}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, MaxF64s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); SetFastMathDisabled(true); auto lhs = builder.ConstantR1({1.0, 1.0, 2.25, NAN, 6.0}); auto rhs = builder.ConstantR1({2.0, -5.0, 1.0, 10.0, NAN}); - auto maximum = builder.Max(lhs, rhs); + builder.Max(lhs, rhs); ComputeAndCompareR1(&builder, {2.0, 1.0, 2.25, NAN, NAN}, {}, error_spec_); @@ -1711,7 +1711,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxF64s) { XLA_TEST_F(ArrayElementwiseOpTest, MaxS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1( {min, min, min, -1, -1, 0, 0, 0, 1, 1, max, max, max}); auto y = builder.ConstantR1( @@ -1726,7 +1726,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxS32s) { XLA_TEST_F(ArrayElementwiseOpTest, MinS32s) { const int32 min = std::numeric_limits::min(); const int32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1( {min, min, min, -1, -1, 0, 0, 0, 1, 1, max, max, max}); auto y = builder.ConstantR1( @@ -1740,7 +1740,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MinS32s) { XLA_TEST_F(ArrayElementwiseOpTest, MaxU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1({0, 0, 1, 1, 1, max, max, max}); auto y = builder.ConstantR1({0, 1, 0, 1, 10, 0, 234234, max}); builder.Max(x, y); @@ -1751,7 +1751,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxU32s) { XLA_TEST_F(ArrayElementwiseOpTest, MinU32s) { const uint32 max = std::numeric_limits::max(); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1({0, 0, 1, 1, 1, max, max, max}); auto y = builder.ConstantR1({0, 1, 0, 1, 10, 0, 234234, max}); builder.Min(x, y); @@ -1761,7 +1761,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MinU32s) { } XLA_TEST_F(ArrayElementwiseOpTest, MaxTenF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1( {-0.0, 1.0, 2.0, -3.0, -4.0, 5.0, 6.0, -7.0, -8.0, 9.0}); auto y = builder.ConstantR1( @@ -1774,7 +1774,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxTenF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, MaxR1S1AndR1S0F32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto u = builder.ConstantR1({3.5}); auto v = builder.ConstantR1({}); builder.Max(u, v); @@ -1784,7 +1784,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxR1S1AndR1S0F32s) { XLA_TEST_F(ArrayElementwiseOpTest, MaxR1S0AndR2S0x2F32s) { for (int broadcast_dim : {0, 1}) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto u = builder.ConstantR1({3.5}); auto v = builder.ConstantR2FromArray2D(Array2D(0, 2)); builder.Max(u, v, /*broadcast_dimensions=*/{broadcast_dim}); @@ -1794,7 +1794,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxR1S0AndR2S0x2F32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Max1DAnd2DF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({2.0f, 3.0f, 4.0f}); auto m = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); @@ -1805,7 +1805,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Max1DAnd2DF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Max1DAnd2DZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({}); auto m = builder.ConstantR2({{}, {}}); builder.Max(v, m, /*broadcast_dimensions=*/{1}); @@ -1815,7 +1815,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Max1DAnd2DZeroElementF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Max3DAndScalarS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto scalar = builder.ConstantR0(2); Array3D a_3d({{{3, 9, -1}, {2, -10, 3}}, {{-2, 2, 8}, {12, 10, 4}}}); auto array = builder.ConstantR3FromArray3D(a_3d); @@ -1826,7 +1826,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Max3DAndScalarS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Max3DAndScalarZeroElementS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto scalar = builder.ConstantR0(2); Array3D a_3d(2, 0, 3); auto array = builder.ConstantR3FromArray3D(a_3d); @@ -1837,7 +1837,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Max3DAndScalarZeroElementS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo1DF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto m = builder.ConstantR2({{-10.4f, 64.0f, 6.0f}, {0.1f, 32.0f, 16.1f}}); auto v = builder.ConstantR1({-10.2f, 16.4f}); @@ -1848,7 +1848,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo1DF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo1DZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto m = builder.ConstantR2({{}, {}}); auto v = builder.ConstantR1({-10.2f, 16.4f}); builder.Min(m, v, /*broadcast_dimensions=*/{0}); @@ -1858,7 +1858,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo1DZeroElementF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo4DF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto array2d = builder.ConstantR2({{-12.2f, 64.3f, 6.1f}, {0.0f, 32.2f, 2.5f}}); auto array4d = builder.ConstantR4FromArray4D( @@ -1873,7 +1873,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo4DF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo4DZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto array2d = builder.ConstantR2({{-12.2f, 64.3f, 6.1f}, {0.0f, 32.2f, 2.5f}}); Array4D arg(2, 2, 0, 3); @@ -1885,7 +1885,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Min2DTo4DZeroElementF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, MinTenS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); auto y = builder.ConstantR1({9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); builder.Min(x, y); @@ -1895,7 +1895,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, MinTenS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, MaxTenS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto x = builder.ConstantR1({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); auto y = builder.ConstantR1({9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); builder.Max(x, y); @@ -1905,10 +1905,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, MaxTenS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, RemTwoConstantS32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-3, 26, 2, -1, 1}); auto b = builder.ConstantR1({10, 5, 1, 10, -10}); - auto add = builder.Rem(a, b); + builder.Rem(a, b); ComputeAndCompareR1(&builder, {-3, 1, 0, -1, 1}, {}); } @@ -2635,7 +2635,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGtR3F32sWithDegenerateDim2) { Array3D b_3d({{{7.0f, 1.0f}, {3.0f, 10.0f}, {15.0f, 6.0f}}}); auto b = builder.ConstantR3FromArray3D(b_3d); - auto compare = builder.Gt(a, b); + builder.Gt(a, b); Array3D expected_3d( {{{0, 1}, {0, 0}, {0, 0}}, {{0, 1}, {1, 0}, {0, 1}}}); diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.h b/tensorflow/compiler/xla/tests/client_library_test_base.h index 01aa6c756f..c39597c4e1 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.h +++ b/tensorflow/compiler/xla/tests/client_library_test_base.h @@ -96,6 +96,9 @@ class ClientLibraryTestBase : public ::testing::Test { ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments); + // TODO(b/74197823): Remove the template type 'BuilderT' in all methods once + // the migration to XlaBuilder is complete. + template StatusOr> ExecuteAndTransfer( BuilderT* builder, tensorflow::gtl::ArraySlice arguments, @@ -127,14 +130,14 @@ class ClientLibraryTestBase : public ::testing::Test { // Convenience methods for building and running a computation, transferring // the result, and comparing it to the expected value(s). Methods are // templated on the native host type which maps to specific XLA types (See - // ComputationBuilder for details). For each rank, two forms are provided: one - // for floating point types with an ErrorSpec parameter, and one for integral - // types without the ErrorSpec parameter. - template - void ComputeAndCompareR0(ComputationBuilder* builder, NativeT expected, + // ComputationBuilder/XlaBuilder for details). For each rank, two forms are + // provided: one for floating point types with an ErrorSpec parameter, and one + // for integral types without the ErrorSpec parameter. + template + void ComputeAndCompareR0(BuilderT* builder, NativeT expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR0(ComputationBuilder* builder, NativeT expected, + template + void ComputeAndCompareR0(BuilderT* builder, NativeT expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error); @@ -154,33 +157,27 @@ class ClientLibraryTestBase : public ::testing::Test { const tensorflow::core::Bitmap& expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR2(ComputationBuilder* builder, - const Array2D& expected, + template + void ComputeAndCompareR2(BuilderT* builder, const Array2D& expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR2(ComputationBuilder* builder, - const Array2D& expected, + template + void ComputeAndCompareR2(BuilderT* builder, const Array2D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error); - template - void ComputeAndCompareR3(ComputationBuilder* builder, - const Array3D& expected, + template + void ComputeAndCompareR3(BuilderT* builder, const Array3D& expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR3(ComputationBuilder* builder, - const Array3D& expected, + template + void ComputeAndCompareR3(BuilderT* builder, const Array3D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error); - template - void ComputeAndCompareR4(ComputationBuilder* builder, - const Array4D& expected, + template + void ComputeAndCompareR4(BuilderT* builder, const Array4D& expected, tensorflow::gtl::ArraySlice arguments); - template - void ComputeAndCompareR4(ComputationBuilder* builder, - const Array4D& expected, + template + void ComputeAndCompareR4(BuilderT* builder, const Array4D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error); @@ -337,10 +334,12 @@ class ClientLibraryTestBase : public ::testing::Test { // // When the use_bfloat16 flag is set but NativeT is float, the data will be // converted to bfloat16. - template - std::unique_ptr CreateR0Parameter( - NativeT value, int64 parameter_number, const string& name, - ComputationBuilder* builder, ComputationDataHandle* data_handle); + template + std::unique_ptr CreateR0Parameter(NativeT value, + int64 parameter_number, + const string& name, + BuilderT* builder, + HandleT* data_handle); // Creates a parameter instruction that wraps the given values and then stores // into "data_handle" the global handle for that parameter. @@ -350,11 +349,10 @@ class ClientLibraryTestBase : public ::testing::Test { // // When the use_bfloat16 flag is set but NativeT is float, the data will be // converted to bfloat16. - template + template std::unique_ptr CreateR1Parameter( tensorflow::gtl::ArraySlice values, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle); + const string& name, BuilderT* builder, HandleT* data_handle); // Creates a parameter instruction that wraps the given constant array // "array_2d" and then stores to "data_handle" the global handle for that @@ -365,11 +363,10 @@ class ClientLibraryTestBase : public ::testing::Test { // // When the use_bfloat16 flag is set but NativeT is float, the data will be // converted to bfloat16. - template + template std::unique_ptr CreateR2Parameter( const Array2D& array_2d, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle); + const string& name, BuilderT* builder, HandleT* data_handle); // Creates a parameter instruction that wraps the given constant array // "array_3d" and then stores to "data_handle" the global handle for that @@ -380,11 +377,10 @@ class ClientLibraryTestBase : public ::testing::Test { // // When the use_bfloat16 flag is set but NativeT is float, the data will be // converted to bfloat16. - template + template std::unique_ptr CreateR3Parameter( const Array3D& array_3d, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle); + const string& name, BuilderT* builder, HandleT* data_handle); // Getter and setter for the use_bfloat16 flag, which indicates whether to run // tests with all float-type input/output converted to bfloat16. @@ -440,9 +436,9 @@ class ClientLibraryTestBase : public ::testing::Test { std::vector> arguments_; }; -template +template void ClientLibraryTestBase::ComputeAndCompareR0( - ComputationBuilder* builder, NativeT expected, + BuilderT* builder, NativeT expected, tensorflow::gtl::ArraySlice arguments) { std::unique_ptr expected_literal = Literal::CreateR0(expected); @@ -450,9 +446,9 @@ void ClientLibraryTestBase::ComputeAndCompareR0( arguments); } -template +template void ClientLibraryTestBase::ComputeAndCompareR0( - ComputationBuilder* builder, NativeT expected, + BuilderT* builder, NativeT expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error) { static_assert(std::is_same::value || std::is_same::value || @@ -492,9 +488,9 @@ void ClientLibraryTestBase::ComputeAndCompareR1( arguments, error); } -template +template void ClientLibraryTestBase::ComputeAndCompareR2( - ComputationBuilder* builder, const Array2D& expected, + BuilderT* builder, const Array2D& expected, tensorflow::gtl::ArraySlice arguments) { std::unique_ptr expected_literal = Literal::CreateR2FromArray2D(expected); @@ -502,9 +498,9 @@ void ClientLibraryTestBase::ComputeAndCompareR2( arguments); } -template +template void ClientLibraryTestBase::ComputeAndCompareR2( - ComputationBuilder* builder, const Array2D& expected, + BuilderT* builder, const Array2D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error) { static_assert(std::is_same::value || std::is_same::value || @@ -518,9 +514,9 @@ void ClientLibraryTestBase::ComputeAndCompareR2( arguments, error); } -template +template void ClientLibraryTestBase::ComputeAndCompareR3( - ComputationBuilder* builder, const Array3D& expected, + BuilderT* builder, const Array3D& expected, tensorflow::gtl::ArraySlice arguments) { std::unique_ptr expected_literal = Literal::CreateR3FromArray3D(expected); @@ -528,9 +524,9 @@ void ClientLibraryTestBase::ComputeAndCompareR3( arguments); } -template +template void ClientLibraryTestBase::ComputeAndCompareR3( - ComputationBuilder* builder, const Array3D& expected, + BuilderT* builder, const Array3D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error) { static_assert(std::is_same::value || std::is_same::value || @@ -544,9 +540,9 @@ void ClientLibraryTestBase::ComputeAndCompareR3( arguments, error); } -template +template void ClientLibraryTestBase::ComputeAndCompareR4( - ComputationBuilder* builder, const Array4D& expected, + BuilderT* builder, const Array4D& expected, tensorflow::gtl::ArraySlice arguments) { std::unique_ptr expected_literal = Literal::CreateR4FromArray4D(expected); @@ -554,9 +550,9 @@ void ClientLibraryTestBase::ComputeAndCompareR4( arguments); } -template +template void ClientLibraryTestBase::ComputeAndCompareR4( - ComputationBuilder* builder, const Array4D& expected, + BuilderT* builder, const Array4D& expected, tensorflow::gtl::ArraySlice arguments, ErrorSpec error) { static_assert(std::is_same::value || std::is_same::value || @@ -570,10 +566,10 @@ void ClientLibraryTestBase::ComputeAndCompareR4( arguments, error); } -template +template std::unique_ptr ClientLibraryTestBase::CreateR0Parameter( NativeT value, int64 parameter_number, const string& name, - ComputationBuilder* builder, ComputationDataHandle* data_handle) { + BuilderT* builder, HandleT* data_handle) { std::unique_ptr literal = Literal::CreateR0(value); if (use_bfloat16_ && literal->shape().element_type() == F32) { literal = LiteralTestUtil::ConvertF32ToBF16(*literal); @@ -584,11 +580,10 @@ std::unique_ptr ClientLibraryTestBase::CreateR0Parameter( return data; } -template +template std::unique_ptr ClientLibraryTestBase::CreateR1Parameter( tensorflow::gtl::ArraySlice values, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle) { + const string& name, BuilderT* builder, HandleT* data_handle) { std::unique_ptr literal = Literal::CreateR1(values); if (use_bfloat16_ && literal->shape().element_type() == F32) { literal = LiteralTestUtil::ConvertF32ToBF16(*literal); @@ -599,11 +594,10 @@ std::unique_ptr ClientLibraryTestBase::CreateR1Parameter( return data; } -template +template std::unique_ptr ClientLibraryTestBase::CreateR2Parameter( const Array2D& array_2d, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle) { + const string& name, BuilderT* builder, HandleT* data_handle) { std::unique_ptr literal = Literal::CreateR2FromArray2D(array_2d); if (use_bfloat16_ && literal->shape().element_type() == F32) { literal = LiteralTestUtil::ConvertF32ToBF16(*literal); @@ -614,11 +608,10 @@ std::unique_ptr ClientLibraryTestBase::CreateR2Parameter( return data; } -template +template std::unique_ptr ClientLibraryTestBase::CreateR3Parameter( const Array3D& array_3d, int64 parameter_number, - const string& name, ComputationBuilder* builder, - ComputationDataHandle* data_handle) { + const string& name, BuilderT* builder, HandleT* data_handle) { std::unique_ptr literal = Literal::CreateR3FromArray3D(array_3d); if (use_bfloat16_ && literal->shape().element_type() == F32) { literal = LiteralTestUtil::ConvertF32ToBF16(*literal); -- GitLab From eee15c1f8ea56dbb516fa9e35392e0a224e99966 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 15:34:21 -0700 Subject: [PATCH 235/906] Update recompute_grad for TPU PiperOrigin-RevId: 190536468 --- .../layers/python/layers/rev_block_lib.py | 105 +++++++++++++++++- .../python/layers/rev_block_lib_test.py | 61 +++++++--- 2 files changed, 146 insertions(+), 20 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/rev_block_lib.py b/tensorflow/contrib/layers/python/layers/rev_block_lib.py index 123275e1fd..0b38c0c3fd 100644 --- a/tensorflow/contrib/layers/python/layers/rev_block_lib.py +++ b/tensorflow/contrib/layers/python/layers/rev_block_lib.py @@ -29,6 +29,7 @@ from __future__ import print_function import functools import re +import numpy as np from six.moves import xrange # pylint: disable=redefined-builtin from tensorflow.contrib.framework.python import ops as contrib_framework_ops @@ -37,6 +38,7 @@ from tensorflow.python.framework import ops as framework_ops from tensorflow.python.layers import base from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import control_flow_util from tensorflow.python.ops import gradients_impl from tensorflow.python.ops import math_ops from tensorflow.python.ops import variable_scope @@ -46,6 +48,7 @@ from tensorflow.python.util import nest __all__ = ["rev_block", "RevBlock", "recompute_grad"] LAYER_RE = re.compile(".*revlayer_([0-9]*)/([fg])/.*") +_USE_DEFAULT = "__rev_block_lib_default" def _acc_grads(*lists_of_grads): @@ -219,7 +222,13 @@ class RevBlock(base.Layer): def _efficient_grad_fn(self, inputs, variables, ys, grad_ys): """Custom gradient fn for a block of reversible residual layers.""" + # Inputs have passed through an Identity. Recover the original Tensors to + # be able to match up side inputs. + assert [u"Identity"] == list(set([x.op.type for x in inputs])) + inputs = [x.op.inputs[0] for x in inputs] side_inputs = inputs[2:] + del inputs + f_side_idxs = [None] * len(self.f_side_input) g_side_idxs = [None] * len(self.g_side_input) assert len(side_inputs) == len(self.f_side_input) + len(self.g_side_input) @@ -405,12 +414,36 @@ def rev_block(x1, return block.forward(x1, x2) -def recompute_grad(fn): +def enable_with_args(dec): + """A decorator for decorators to enable their usage with or without args.""" + + @functools.wraps(dec) + def new_dec(*args, **kwargs): + if len(args) == 1 and not kwargs and callable(args[0]): + # Used as decorator without args + fn = args[0] + return dec(fn) + else: + return lambda fn: dec(fn, *args, **kwargs) + + return new_dec + + +@enable_with_args +def recompute_grad(fn, use_data_dep=_USE_DEFAULT, tupleize_grads=False): """Decorator that recomputes the function on the backwards pass. Args: fn: a function that takes Tensors (all as positional arguments) and returns a tuple of Tensors. + use_data_dep: `bool`, if `True` will use a dummy data dependency to force + the recompute to happen. If `False` will use a control dependency. By + default will be `True` if in an XLA context and `False` otherwise. XLA + ignores control dependencies and so this data dependency is necessary. + tupleize_grads: `bool`, if `True` will use control dependencies to ensure + that all gradients are produced before any are consumed by downstream ops. + If `use_data_dep` is also `True`, will use a data dependency instead of + a control dependency. Returns: A wrapped fn that is identical to fn when called, but its activations will @@ -420,13 +453,25 @@ def recompute_grad(fn): @functools.wraps(fn) def wrapped(*args): - return _recompute_grad(fn, args) + return _recompute_grad( + fn, args, use_data_dep=use_data_dep, tupleize_grads=tupleize_grads) return wrapped -def _recompute_grad(fn, args): +def _is_on_tpu(): + ctxt = framework_ops.get_default_graph()._get_control_flow_context() # pylint: disable=protected-access + return control_flow_util.GetContainingXLAContext(ctxt) is not None + + +def _recompute_grad(fn, args, use_data_dep=_USE_DEFAULT, tupleize_grads=False): """See recompute_grad.""" + for arg in args: + if not isinstance(arg, framework_ops.Tensor): + raise ValueError("All inputs to function must be Tensors") + use_data_dep_ = use_data_dep + if use_data_dep_ == _USE_DEFAULT: + use_data_dep_ = _is_on_tpu() cached_vs = [] cached_arg_scope = [] @@ -436,6 +481,8 @@ def _recompute_grad(fn, args): del outputs # Recompute outputs with framework_ops.control_dependencies(output_grads): + if use_data_dep_: + inputs = _force_data_dependency(output_grads, inputs) with contrib_framework_ops.arg_scope(cached_arg_scope[0]): with variable_scope.variable_scope(cached_vs[0], reuse=True): outputs = fn(*inputs) @@ -444,6 +491,13 @@ def _recompute_grad(fn, args): outputs = [outputs] outputs = list(outputs) grads = gradients_impl.gradients(outputs, inputs + variables, output_grads) + + if tupleize_grads: + if use_data_dep_: + grads = _tuple_with_data_dep(grads) + else: + grads = control_flow_ops.tuple(grads) + grad_inputs = grads[:len(inputs)] grad_vars = grads[len(inputs):] return grad_inputs, grad_vars @@ -532,7 +586,7 @@ def _fn_with_custom_grad_internal(fn, inputs, grad_fn, use_global_vars=False): get_vars_fn = ( vs.global_variables if use_global_vars else vs.trainable_variables) len_before_vars = len(get_vars_fn()) - inputs = list(inputs) + inputs = [array_ops.identity(x) for x in inputs] outputs = fn(*inputs) train_vars = get_vars_fn()[len_before_vars:] @@ -581,3 +635,46 @@ def _fn_with_custom_grad_internal(fn, inputs, grad_fn, use_global_vars=False): flat_inputs = nest.flatten(defun_inputs) id_out = identity(*flat_inputs) return id_out + + +def _force_data_dependency(first_compute, then_compute): + """Force all of `then_compute` to depend on all of `first_compute`. + + Uses a dummy data dependency, which is useful when running on TPUs because + XLA ignores control dependencies. Only supports float arguments. + + Args: + first_compute: `list`. These will be made to run before the + `Tensor`s `then_compute`. + then_compute: `list`. These will run after all the `Tensor`s in + `first_compute`. + + Returns: + `list`, same length as `then_compute`. + + Raises: + ValueError: if ranks are unknown or types are not floating. + """ + + def _first_element(x): + if x.get_shape().ndims is None: + raise ValueError("Rank of Tensor %s must be known" % x) + ndims = x.get_shape().ndims + return array_ops.reshape(array_ops.slice(x, [0] * ndims, [1] * ndims), []) + + first_compute_sum = math_ops.add_n( + [_first_element(x) for x in first_compute if x is not None]) + dtype = first_compute_sum.dtype + if not dtype.is_floating: + raise ValueError("_force_data_dependency only supports floating dtypes.") + epsilon = np.finfo(dtype.as_numpy_dtype).tiny + zero = array_ops.stop_gradient(epsilon * first_compute_sum) + + return [ + array_ops.identity(x) + zero if x is not None else None + for x in then_compute + ] + + +def _tuple_with_data_dep(tensors): + return _force_data_dependency(tensors, tensors) diff --git a/tensorflow/contrib/layers/python/layers/rev_block_lib_test.py b/tensorflow/contrib/layers/python/layers/rev_block_lib_test.py index cbcbcd7511..d1ad4e8c98 100644 --- a/tensorflow/contrib/layers/python/layers/rev_block_lib_test.py +++ b/tensorflow/contrib/layers/python/layers/rev_block_lib_test.py @@ -154,7 +154,7 @@ class RevBlockTest(test.TestCase): y_val, yd_val, gd_val, g_val = sess.run([y, y_rev, grads_rev, grads]) self.assertAllClose(y_val, yd_val) for g1, g2 in zip(gd_val, g_val): - self.assertAllClose(g1, g2) + self.assertAllClose(g1, g2, rtol=1e-5) def testRevBlock(self): self._testRevBlock() @@ -255,25 +255,54 @@ class RecomputeTest(test.TestCase): def fn_recompute(x): return fn(x) + @rev_block_lib.recompute_grad(use_data_dep=True) + def fn_use_data_dep(x): + return fn(x) + + @rev_block_lib.recompute_grad(tupleize_grads=True) + def fn_tupleize(x): + return fn(x) + + @rev_block_lib.recompute_grad(use_data_dep=True, tupleize_grads=True) + def fn_both(x): + return fn(x) + x = random_ops.random_uniform((3, 1, 3)) - recompute_vars = None - with variable_scope.variable_scope("recompute") as vs: - out1 = math_ops.reduce_sum(fn_recompute(x)) - recompute_vars = vs.trainable_variables() - reg_vars = None - with variable_scope.variable_scope("regular") as vs: - out2 = math_ops.reduce_sum(fn(x)) - reg_vars = vs.trainable_variables() - - grad1 = gradients_impl.gradients(out1, recompute_vars) - grad2 = gradients_impl.gradients(out2, reg_vars) + + names_and_fns = [ + ("recompute", fn_recompute), + ("regular", fn), + ("use_data_dep", fn_use_data_dep), + ("tupleize", fn_tupleize), + ("tuple_and_data_dep", fn_both), + ] + outputs_and_vars = [] + for name, wrapped_fn in names_and_fns: + with variable_scope.variable_scope(name) as vs: + out = math_ops.reduce_sum(wrapped_fn(x)) + outputs_and_vars.append((out, vs.trainable_variables())) + + all_grads = [] + for out, scope_vars in outputs_and_vars: + all_grads.append(gradients_impl.gradients(out, scope_vars)) with self.test_session() as sess: sess.run(variables.global_variables_initializer()) - outs = sess.run([out1, out2, grad1, grad2]) - self.assertAllClose(outs[0], outs[1]) - for g1, g2 in zip(outs[2], outs[3]): - self.assertAllClose(g1, g2) + outputs = list(zip(*outputs_and_vars))[0] + outs, all_grads_val = sess.run([outputs, all_grads]) + + # All outputs are the same + current = outs[0] + for out in outs[1:]: + self.assertAllClose(current, out) + current = out + + # All gradients are the same + for grads in zip(all_grads_val): + current = grads[0] + for g in grads[1:]: + self.assertAllClose(current, g) + current = g class FnWithCustomGradTest(test.TestCase): -- GitLab From 290632966fae0619db30c1ba777634db9a43b757 Mon Sep 17 00:00:00 2001 From: Mingsheng Hong Date: Mon, 26 Mar 2018 15:37:40 -0700 Subject: [PATCH 236/906] In the experimental C API, parametrized batch_size for the generate dataset / iterator stack. PiperOrigin-RevId: 190536945 --- tensorflow/c/c_api_experimental.cc | 67 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc index f411efc941..bea9378571 100644 --- a/tensorflow/c/c_api_experimental.cc +++ b/tensorflow/c/c_api_experimental.cc @@ -7125,7 +7125,8 @@ library { // sets `dataset_name` to the created dataset name. The returned functions must // be deleted by calling TF_DeleteFunction. static std::vector CreateMNISTDatasetFunctions( - const char* file_path, std::string* dataset_name, TF_Status* status) { + const char* file_path, int batch_size, std::string* dataset_name, + TF_Status* status) { const char* func_def = R"PREFIX( library { function { @@ -8089,7 +8090,7 @@ library { dtype: DT_INT64 tensor_shape { } - int64_val: 128 + int64_val: -123 } } } @@ -8145,7 +8146,7 @@ library { dtype: DT_INT64 tensor_shape { } - int64_val: 128 + int64_val: -123 } } } @@ -8211,35 +8212,48 @@ library { *dataset_name = "_make_dataset_2451e43a"; std::function mutate_proto_func = - [dataset_name, file_path](FunctionDef* fdef) { + [dataset_name, file_path, batch_size](FunctionDef* fdef) { VLOG(1) << "Processsing function " << fdef->DebugString(); if (std::string(fdef->signature().name()) != *dataset_name) return; // Change the input file pattern to `file_path`. - bool found = false; + bool found_file_path = false, found_batch_size = false; // `node_def` may be mutated. for (auto& node_def : *fdef->mutable_node_def()) { - if (node_def.name() != "FixedLengthRecordDataset/filenames" && - node_def.name() != "FixedLengthRecordDataset_1/filenames_1") - continue; - DCHECK_EQ(node_def.op(), "Const"); - DCHECK_GT(node_def.attr().count("value"), 0); - found = true; - // Replace $(DATA_DIR)/foo with /foo - // TODO(hongm): Use StringPiece manipulation for better efficiency. - const std::string cur_value = - node_def.attr().at("value").tensor().string_val(0); - const std::string pattern = "$(DATA_DIR)"; - DCHECK_EQ(cur_value.compare(0, pattern.length(), pattern), 0); - const std::string new_value = - file_path + cur_value.substr(pattern.length()); - VLOG(1) << "Setting the value of node_def " << node_def.name() - << " to " << new_value; - auto* tensor = (*node_def.mutable_attr())["value"].mutable_tensor(); - tensor->clear_string_val(); - tensor->add_string_val(new_value); + if (node_def.name() == "FixedLengthRecordDataset/filenames" || + node_def.name() == "FixedLengthRecordDataset_1/filenames_1") { + DCHECK_EQ(node_def.op(), "Const"); + DCHECK_GT(node_def.attr().count("value"), 0); + found_file_path = true; + // Replace $(DATA_DIR)/foo with /foo + // TODO(hongm): Use StringPiece manipulation for better efficiency. + const std::string cur_value = + node_def.attr().at("value").tensor().string_val(0); + const std::string pattern = "$(DATA_DIR)"; + DCHECK_EQ(cur_value.compare(0, pattern.length(), pattern), 0); + const std::string new_value = + file_path + cur_value.substr(pattern.length()); + VLOG(1) << "Setting the value of node_def " << node_def.name() + << " to " << new_value; + auto* tensor = (*node_def.mutable_attr())["value"].mutable_tensor(); + tensor->clear_string_val(); + tensor->add_string_val(new_value); + } else if (node_def.name() == "BatchDataset/batch_size" || + node_def.name() == "FilterDataset/batch_size_1") { + DCHECK_EQ(node_def.op(), "Const"); + DCHECK_GT(node_def.attr().count("value"), 0); + found_batch_size = true; + // Replace $(BATCH_SIZE) with `batch_size` + DCHECK_EQ(node_def.attr().at("value").tensor().int64_val(0), -123); + VLOG(1) << "Setting the batch size attr value of node_def " + << node_def.name() << " to " << batch_size; + auto* tensor = (*node_def.mutable_attr())["value"].mutable_tensor(); + tensor->clear_int64_val(); + tensor->add_int64_val(batch_size); + } } VLOG(1) << "Rewrote function to " << fdef->DebugString(); - DCHECK(found); + DCHECK(found_file_path); + DCHECK(found_batch_size); }; return CreateFunctionsFromTextProto(func_def, &mutate_proto_func, status); } @@ -8341,7 +8355,8 @@ TF_Operation* TF_MakeFileBasedIteratorGetNextWithDatasets( std::string dataset_name; const auto& funcs = is_mnist - ? CreateMNISTDatasetFunctions(file_path, &dataset_name, status) + ? CreateMNISTDatasetFunctions(file_path, batch_size, &dataset_name, + status) : CreateImagenetDatasetFunctions(file_path, &dataset_name, status); if (!status->status.ok()) { return nullptr; -- GitLab From c83a54adcface7d4bb666d7c4fd3968ba980a50d Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Mon, 26 Mar 2018 15:39:54 -0700 Subject: [PATCH 237/906] Makes tf.gather not silently snapshot resource variables. PiperOrigin-RevId: 190537320 --- .../kernel_tests/attention_wrapper_test.py | 29 +++++++++++-------- tensorflow/python/ops/array_ops.py | 17 +++++++---- tensorflow/python/ops/embedding_ops.py | 29 ++++--------------- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/tensorflow/contrib/seq2seq/python/kernel_tests/attention_wrapper_test.py b/tensorflow/contrib/seq2seq/python/kernel_tests/attention_wrapper_test.py index c4139dde49..07b3ad71d4 100644 --- a/tensorflow/contrib/seq2seq/python/kernel_tests/attention_wrapper_test.py +++ b/tensorflow/contrib/seq2seq/python/kernel_tests/attention_wrapper_test.py @@ -785,26 +785,31 @@ class AttentionWrapperTest(test.TestCase): wrapper.BahdanauAttention, wrapper.LuongAttention) expected_final_output = BasicDecoderOutput( - rnn_output=ResultSummary( - shape=(5, 3, 20), dtype=dtype('float32'), mean=0.11798714846372604), - sample_id=ResultSummary( - shape=(5, 3), dtype=dtype('int32'), mean=7.933333333333334)) + rnn_output=ResultSummary(shape=(5, 3, 20), + dtype=dtype('float32'), + mean=0.11723966), + sample_id=ResultSummary(shape=(5, 3), + dtype=dtype('int32'), + mean=9.2666666666666675)) expected_final_state = AttentionWrapperState( cell_state=LSTMStateTuple( - c=ResultSummary( - shape=(5, 9), dtype=dtype('float32'), mean=-0.0036486709), - h=ResultSummary( - shape=(5, 9), dtype=dtype('float32'), mean=-0.0018835809)), - attention=ResultSummary( - shape=(5, 20), dtype=dtype('float32'), mean=0.11798714846372604), + c=ResultSummary(shape=(5, 9), + dtype=dtype('float32'), + mean=-0.003545674), + h=ResultSummary(shape=(5, 9), + dtype=dtype('float32'), + mean=-0.0018327223)), + attention=ResultSummary(shape=(5, 20), + dtype=dtype('float32'), + mean=0.11728073), time=3, alignments=( ResultSummary(shape=(5, 8), dtype=dtype('float32'), mean=0.125), ResultSummary(shape=(5, 8), dtype=dtype('float32'), mean=0.125)), + alignment_history=(), attention_state=( ResultSummary(shape=(5, 8), dtype=dtype('float32'), mean=0.125), - ResultSummary(shape=(5, 8), dtype=dtype('float32'), mean=0.125)), - alignment_history=()) + ResultSummary(shape=(5, 8), dtype=dtype('float32'), mean=0.125))) expected_final_alignment_history = ( ResultSummary(shape=(3, 5, 8), dtype=dtype('float32'), mean=0.125), ResultSummary(shape=(3, 5, 8), dtype=dtype('float32'), mean=0.125)) diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index ec7c14f7d8..9106461c60 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -2691,12 +2691,17 @@ reverse_sequence.__doc__ = deprecation.rewrite_argument_docstring( @tf_export("gather") def gather(params, indices, validate_indices=None, name=None, axis=0): - # TODO(rjryan): Remove "Gather" creation in favor of GatherV2 once the forward - # compatibility 3 week period has passed. - if axis == 0: - return gen_array_ops.gather( - params, indices, validate_indices=validate_indices, name=name) - else: + del validate_indices + if axis != 0: + # Note that we do a sparse_read here to avoid snapshotting the entire + # resource variable and doing a gather, which can be inefficient and lead to + # subtle race conditions. TODO(apassos) implement axis != 0 on sparse_read + return gen_array_ops.gather_v2(params, indices, axis, name=name) + try: + # TODO(apassos) find a less bad way of detecting resource variables without + # introducing a circular dependency. + return params.sparse_read(indices, name=name) + except AttributeError: return gen_array_ops.gather_v2(params, indices, axis, name=name) diff --git a/tensorflow/python/ops/embedding_ops.py b/tensorflow/python/ops/embedding_ops.py index 20e4a28b9c..f0120f2957 100644 --- a/tensorflow/python/ops/embedding_ops.py +++ b/tensorflow/python/ops/embedding_ops.py @@ -35,34 +35,14 @@ from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util.tf_export import tf_export -def _gather(params, ids, name=None): - """Helper function for _embedding_lookup_and_transform. - - This function gathers embeddings from a single tensor. The gather deals with - resource variables specially. - - Args: - params: A `Tensor` of embeddings. - ids: A `Tensor` indexing the embeddings to be retrieved from `params`. - name: A name for the operation (optional). - - Returns: - A `Tensor` with the same type as `params`. - """ - if isinstance(params, resource_variable_ops.ResourceVariable): - return params.sparse_read(ids, name=name) - else: - return array_ops.gather(params, ids, name=name) - - def _clip(params, ids, max_norm): """Helper function for _embedding_lookup_and_transform. This function optionally clips embeddings to an l2-norm of max_norm. Args: - params: A `Tensor` of embeddings retrieved by `_gather`. - ids: The `ids` argument that was passed to `_gather`. + params: A `Tensor` of embeddings retrieved by `gather`. + ids: The `ids` argument that was passed to `gather`. max_norm: If provided, the embeddings are l2-normalized to the value of max_norm. @@ -148,7 +128,8 @@ def _embedding_lookup_and_transform(params, ids = ops.convert_to_tensor(ids, name="ids") if np == 1 and (not transform_fn or ids.get_shape().ndims == 1): with ops.colocate_with(params[0]): - result = _clip(_gather(params[0], ids, name=name), ids, max_norm) + result = _clip(array_ops.gather(params[0], ids, name=name), + ids, max_norm) if transform_fn: result = transform_fn(result) return result @@ -212,7 +193,7 @@ def _embedding_lookup_and_transform(params, for p in xrange(np): pids = gather_ids[p] with ops.colocate_with(params[p]): - result = _gather(params[p], pids) + result = array_ops.gather(params[p], pids) if transform_fn: # If transform_fn is provided, the clip_by_norm precedes # the transform and hence must be co-located. See below -- GitLab From db076ca01f12368c9476fa4db9d87756f22f9670 Mon Sep 17 00:00:00 2001 From: Suharsh Sivakumar Date: Mon, 26 Mar 2018 15:52:12 -0700 Subject: [PATCH 238/906] Rename convert_savedmodel to convert_saved_model to be consistent with export_saved_model PiperOrigin-RevId: 190539064 --- tensorflow/contrib/lite/python/BUILD | 12 ++++++------ ...vert_savedmodel.py => convert_saved_model.py} | 15 ++++++++------- ...model_test.py => convert_saved_model_test.py} | 16 ++++++++-------- 3 files changed, 22 insertions(+), 21 deletions(-) rename tensorflow/contrib/lite/python/{convert_savedmodel.py => convert_saved_model.py} (96%) rename tensorflow/contrib/lite/python/{convert_savedmodel_test.py => convert_saved_model_test.py} (96%) diff --git a/tensorflow/contrib/lite/python/BUILD b/tensorflow/contrib/lite/python/BUILD index ce1a81d06b..411d5c0d27 100644 --- a/tensorflow/contrib/lite/python/BUILD +++ b/tensorflow/contrib/lite/python/BUILD @@ -85,8 +85,8 @@ py_test( ) py_binary( - name = "convert_savedmodel", - srcs = ["convert_savedmodel.py"], + name = "convert_saved_model", + srcs = ["convert_saved_model.py"], srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ @@ -98,12 +98,12 @@ py_binary( ) py_test( - name = "convert_savedmodel_test", - srcs = ["convert_savedmodel_test.py"], + name = "convert_saved_model_test", + srcs = ["convert_saved_model_test.py"], srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ - ":convert_savedmodel", + ":convert_saved_model", "//tensorflow/python:client_testlib", "//tensorflow/python:platform_test", "//tensorflow/python:session", @@ -115,7 +115,7 @@ py_test( py_library( name = "tf_lite_py_pip", deps = [ - ":convert_savedmodel", + ":convert_saved_model", ], ) diff --git a/tensorflow/contrib/lite/python/convert_savedmodel.py b/tensorflow/contrib/lite/python/convert_saved_model.py similarity index 96% rename from tensorflow/contrib/lite/python/convert_savedmodel.py rename to tensorflow/contrib/lite/python/convert_saved_model.py index d39e1a1d98..a2b5ef488e 100644 --- a/tensorflow/contrib/lite/python/convert_savedmodel.py +++ b/tensorflow/contrib/lite/python/convert_saved_model.py @@ -16,7 +16,7 @@ r"""TensorFlow Lite flatbuffer generation from saved_models. Example: -bazel run third_party/tensorflow/contrib/lite/python:convert_savedmodel -- \ +bazel run third_party/tensorflow/contrib/lite/python:convert_saved_model -- \ --saved_model_dir=/tmp/test_saved_model/1519865537 \ --output_tflite=/tmp/test.lite @@ -68,16 +68,16 @@ def log_tensor_details(tensor_info): dims = [str(dim.size) for dim in val.tensor_shape.dim] shape = "({})".format(", ".join(dims)) - logging.info("Tensor's key in savedmodel's tensor_map: %s", key) + logging.info("Tensor's key in saved_model's tensor_map: %s", key) logging.info(" tensor name: %s, shape: %s, type: %s", val.name, shape, dtype) def get_meta_graph_def(saved_model_dir, tag_set): - """Validate savedmodel and extract MetaGraphDef. + """Validate saved_model and extract MetaGraphDef. Args: - saved_model_dir: Savedmodel path to convert. + saved_model_dir: saved_model path to convert. tag_set: Set of tag(s) of the MetaGraphDef to load. Returns: @@ -94,7 +94,8 @@ def get_meta_graph_def(saved_model_dir, tag_set): tag_sets.append(meta_graph_tag_set) if meta_graph_tag_set == tag_set: result_meta_graph_def = meta_graph_def - logging.info("The given SavedModel contains the following tags: %s", tag_sets) + logging.info("The given saved_model contains the following tags: %s", + tag_sets) if result_meta_graph_def is not None: return result_meta_graph_def else: @@ -118,7 +119,7 @@ def get_signature_def(meta_graph, signature_key): signature_def_map = meta_graph.signature_def signature_def_keys = set(signature_def_map.keys()) logging.info( - "The given SavedModel MetaGraphDef contains SignatureDefs with the " + "The given saved_model MetaGraphDef contains SignatureDefs with the " "following keys: %s", signature_def_keys) if signature_key not in signature_def_keys: raise ValueError("No '{}' in the saved_model\'s SignatureDefs. Possible " @@ -159,7 +160,7 @@ def convert(saved_model_dir, tag_set=None, signature_key=signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY, batch_size=1): - """Convert a savedmodel to tflite flatbuffer. + """Convert a saved_model to tflite flatbuffer. Args: saved_model_dir: Saved model directory to convert. diff --git a/tensorflow/contrib/lite/python/convert_savedmodel_test.py b/tensorflow/contrib/lite/python/convert_saved_model_test.py similarity index 96% rename from tensorflow/contrib/lite/python/convert_savedmodel_test.py rename to tensorflow/contrib/lite/python/convert_saved_model_test.py index 70cff9ef7f..d87fbeb91c 100644 --- a/tensorflow/contrib/lite/python/convert_savedmodel_test.py +++ b/tensorflow/contrib/lite/python/convert_saved_model_test.py @@ -24,7 +24,7 @@ from __future__ import division from __future__ import print_function import os -from tensorflow.contrib.lite.python import convert_savedmodel +from tensorflow.contrib.lite.python import convert_saved_model from tensorflow.python import estimator from tensorflow.python import keras from tensorflow.python import layers @@ -60,13 +60,13 @@ class ConvertSavedModelTestBasicGraph(test_util.TensorFlowTestCase): # Create a simple savedmodel saved_model_dir = self._createSimpleSavedModel(shape=[1, 16, 16, 3]) # Convert to tflite - result = convert_savedmodel.convert(saved_model_dir=saved_model_dir) + result = convert_saved_model.convert(saved_model_dir=saved_model_dir) self.assertTrue(result) def testSimpleSavedModelWithNoneBatchSizeInShape(self): """Test a simple savedmodel, with None in input tensor's shape.""" saved_model_dir = self._createSimpleSavedModel(shape=[None, 16, 16, 3]) - result = convert_savedmodel.convert(saved_model_dir=saved_model_dir) + result = convert_saved_model.convert(saved_model_dir=saved_model_dir) self.assertTrue(result) def testSimpleSavedModelWithMoreNoneInShape(self): @@ -74,7 +74,7 @@ class ConvertSavedModelTestBasicGraph(test_util.TensorFlowTestCase): saved_model_dir = self._createSimpleSavedModel(shape=[None, 16, None, 3]) # Convert to tflite: this should raise ValueError, as 3rd dim is None. with self.assertRaises(ValueError): - convert_savedmodel.convert(saved_model_dir=saved_model_dir) + convert_saved_model.convert(saved_model_dir=saved_model_dir) def testSimpleSavedModelWithWrongSignatureKey(self): """Test a simple savedmodel, fail as given signature is invalid.""" @@ -82,7 +82,7 @@ class ConvertSavedModelTestBasicGraph(test_util.TensorFlowTestCase): # Convert to tflite: this should raise ValueError, as # signature_key does not exit in the saved_model. with self.assertRaises(ValueError): - convert_savedmodel.convert( + convert_saved_model.convert( saved_model_dir=saved_model_dir, signature_key="wrong-key") def testSimpleSavedModelWithWrongOutputArray(self): @@ -92,7 +92,7 @@ class ConvertSavedModelTestBasicGraph(test_util.TensorFlowTestCase): # Convert to tflite: this should raise ValueError, as # output_arrays is not valid for the saved_model. with self.assertRaises(ValueError): - convert_savedmodel.convert( + convert_saved_model.convert( saved_model_dir=saved_model_dir, output_arrays="wrong-output") def testMultipleMetaGraphDef(self): @@ -124,7 +124,7 @@ class ConvertSavedModelTestBasicGraph(test_util.TensorFlowTestCase): builder.save(True) # Convert to tflite - convert_savedmodel.convert( + convert_saved_model.convert( saved_model_dir=saved_model_dir, tag_set=set([saved_model.tag_constants.SERVING, "additional_test_tag"])) @@ -264,7 +264,7 @@ class ConvertSavedModelTestTrainGraph(test_util.TensorFlowTestCase): saved_model_final_dir + ".lite") # TODO(zhixianyan): no need to limit output_arrays to `Softmax' # once b/74205001 fixed and argmax implemented in tflite. - result = convert_savedmodel.convert( + result = convert_saved_model.convert( saved_model_dir=saved_model_final_dir, output_arrays="Softmax", output_tflite=output_tflite) -- GitLab From 73f40467bde137e2e2b31297b73944cc2830bdb7 Mon Sep 17 00:00:00 2001 From: Ou Changkun Date: Tue, 27 Mar 2018 00:57:52 +0200 Subject: [PATCH 239/906] Fix missing interpretation of document (#17990) * Fix missing interpretation of document * Rephrase the sentence of missing interpretation --- tensorflow/docs_src/mobile/optimizing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/docs_src/mobile/optimizing.md b/tensorflow/docs_src/mobile/optimizing.md index ca9cb043e9..778e4d3a62 100644 --- a/tensorflow/docs_src/mobile/optimizing.md +++ b/tensorflow/docs_src/mobile/optimizing.md @@ -233,6 +233,8 @@ order by how long they took. From left to right, the columns are: - The cumulative total time of this and the previous ops in the table. This is handy for understanding what the distribution of work is across the layers, to see if just a few of the nodes are taking up most of the time. + +- The amount of memory consumed by outputs of this type of op. - Name of the node. -- GitLab From e5dcaf921cf9feefd42b2ab176590c696b3b0285 Mon Sep 17 00:00:00 2001 From: Jerry Liu Date: Tue, 27 Mar 2018 07:21:54 +0800 Subject: [PATCH 240/906] Fix #15900 (#16154) - Added `save_checkpoint_steps` attribute to `MonitoredTrainingSession`. If both `save_checkpoint_steps` and `save_checkpoint_secs` are both `None` then default saver is disabled. Default is `save_checkpoint_secs=600` - Added `test_save_checkpoint_steps` - Updated golden file --- .../python/training/monitored_session.py | 33 +++++++++++++---- .../python/training/monitored_session_test.py | 36 +++++++++++++++++++ .../tools/api/golden/tensorflow.train.pbtxt | 2 +- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/tensorflow/python/training/monitored_session.py b/tensorflow/python/training/monitored_session.py index 6c5c9e01a7..2d4f09a60a 100644 --- a/tensorflow/python/training/monitored_session.py +++ b/tensorflow/python/training/monitored_session.py @@ -281,13 +281,14 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name scaffold=None, hooks=None, chief_only_hooks=None, - save_checkpoint_secs=600, + save_checkpoint_secs=USE_DEFAULT, save_summaries_steps=USE_DEFAULT, save_summaries_secs=USE_DEFAULT, config=None, stop_grace_period_secs=120, log_step_count_steps=100, - max_wait_secs=7200): + max_wait_secs=7200, + save_checkpoint_steps=USE_DEFAULT): """Creates a `MonitoredSession` for training. For a chief, this utility sets proper session initializer/restorer. It also @@ -310,8 +311,10 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name chief_only_hooks: list of `SessionRunHook` objects. Activate these hooks if `is_chief==True`, ignore otherwise. save_checkpoint_secs: The frequency, in seconds, that a checkpoint is saved - using a default checkpoint saver. If `save_checkpoint_secs` is set to - `None`, then the default checkpoint saver isn't used. + using a default checkpoint saver. If both `save_checkpoint_steps` and + `save_checkpoint_secs` are set to `None`, then the default checkpoint + saver isn't used. If both are provided, then only `save_checkpoint_secs` + is used. Default 600. save_summaries_steps: The frequency, in number of global steps, that the summaries are written to disk using a default summary saver. If both `save_summaries_steps` and `save_summaries_secs` are set to `None`, then @@ -330,6 +333,11 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name become available. This should be kept relatively short to help detect incorrect code, but sometimes may need to be increased if the chief takes a while to start up. + save_checkpoint_steps: The frequency, in number of global steps, that a + checkpoint is saved using a default checkpoint saver. If both + `save_checkpoint_steps` and `save_checkpoint_secs` are set to `None`, then + the default checkpoint saver isn't used. If both are provided, then only + `save_checkpoint_secs` is used. Default not enabled. Returns: A `MonitoredSession` object. @@ -342,6 +350,15 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name elif save_summaries_steps == USE_DEFAULT: save_summaries_steps = None + if save_checkpoint_steps == USE_DEFAULT and \ + save_checkpoint_secs == USE_DEFAULT: + save_checkpoint_steps = None + save_checkpoint_secs = 600 + elif save_checkpoint_secs == USE_DEFAULT: + save_checkpoint_secs = None + elif save_checkpoint_steps == USE_DEFAULT: + save_checkpoint_steps = None + scaffold = scaffold or Scaffold() if not is_chief: session_creator = WorkerSessionCreator( @@ -374,9 +391,13 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name save_steps=save_summaries_steps, save_secs=save_summaries_secs, output_dir=checkpoint_dir)) - if save_checkpoint_secs and save_checkpoint_secs > 0: + if (save_checkpoint_secs and save_checkpoint_secs > 0) or ( + save_checkpoint_steps and save_checkpoint_steps > 0): all_hooks.append(basic_session_run_hooks.CheckpointSaverHook( - checkpoint_dir, save_secs=save_checkpoint_secs, scaffold=scaffold)) + checkpoint_dir, + save_steps=save_checkpoint_steps, + save_secs=save_checkpoint_secs, + scaffold=scaffold)) if hooks: all_hooks.extend(hooks) diff --git a/tensorflow/python/training/monitored_session_test.py b/tensorflow/python/training/monitored_session_test.py index 159b2d5c16..3806056f01 100644 --- a/tensorflow/python/training/monitored_session_test.py +++ b/tensorflow/python/training/monitored_session_test.py @@ -282,6 +282,42 @@ class MonitoredTrainingSessionTest(test.TestCase): is_chief=True, checkpoint_dir=logdir) as session: self.assertEqual(2, session.run(gstep)) + def test_save_checkpoint_steps(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_steps') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_steps=100, + log_step_count_steps=10) as session: + for _ in range(100): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(100, session.run(gstep)) + + def test_save_checkpoint_secs(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_secs') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_secs=0.1, + log_step_count_steps=10) as session: + session.run(new_gstep) + time.sleep(0.2) + for _ in range(10): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(11, session.run(gstep)) + def test_summaries_steps(self): logdir = _test_dir(self.get_temp_dir(), 'test_summaries_steps') with ops.Graph().as_default(): diff --git a/tensorflow/tools/api/golden/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/tensorflow.train.pbtxt index c75ee474aa..bec72e1e60 100644 --- a/tensorflow/tools/api/golden/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.train.pbtxt @@ -238,7 +238,7 @@ tf_module { } member_method { name: "MonitoredTrainingSession" - argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'600\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\'], " + argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\', \'save_checkpoint_steps\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\', \'\'], " } member_method { name: "NewCheckpointReader" -- GitLab From 307cfe7ab7e2c475b2741fc2a2f7663b46223e6d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 16:19:50 -0700 Subject: [PATCH 241/906] Save the last loss reduction method (for future use). PiperOrigin-RevId: 190543066 --- tensorflow/python/framework/ops.py | 3 +++ tensorflow/python/ops/losses/losses_impl.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index e579289a8d..25a951a2de 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -2788,6 +2788,9 @@ class Graph(object): # being called inside function definitions behave as if they were seeing the # actual outside graph). self._graph_key = "grap-key-%d/" % (uid(),) + # A string with the last reduction method passed to + # losses.compute_weighted_loss(), or None. + self._last_loss_reduction = None self._container = "" self._registered_ops = op_def_registry.get_registered_ops() diff --git a/tensorflow/python/ops/losses/losses_impl.py b/tensorflow/python/ops/losses/losses_impl.py index 0840760810..34ca1adc3e 100644 --- a/tensorflow/python/ops/losses/losses_impl.py +++ b/tensorflow/python/ops/losses/losses_impl.py @@ -194,6 +194,11 @@ def compute_weighted_loss( """ Reduction.validate(reduction) with ops.name_scope(scope, "weighted_loss", (losses, weights)): + # Save the `reduction` argument for loss normalization when distributing + # to multiple towers. + # TODO(josh11b): Associate it with the returned op for more precision. + ops.get_default_graph()._last_loss_reduction = reduction # pylint: disable=protected-access + with ops.control_dependencies(( weights_broadcast_ops.assert_broadcastable(weights, losses),)): losses = ops.convert_to_tensor(losses) -- GitLab From eda7aa3f7e763734f5f3550bed8b044a384b2ce8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 17:02:55 -0700 Subject: [PATCH 242/906] Add missing parameter to OP_REQUIRES call. PiperOrigin-RevId: 190548854 --- tensorflow/core/kernels/mkl_reshape_op.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/mkl_reshape_op.cc b/tensorflow/core/kernels/mkl_reshape_op.cc index e12f6f437a..2cfde1f6fd 100644 --- a/tensorflow/core/kernels/mkl_reshape_op.cc +++ b/tensorflow/core/kernels/mkl_reshape_op.cc @@ -266,8 +266,9 @@ class MklReshapeOp : public OpKernel { &net)) { stream(stream::kind::eager).submit(net).wait(); } else { - OP_REQUIRES(context, - output_tensor->CopyFrom(input_tensor, shape_to)); + OP_REQUIRES( + context, output_tensor->CopyFrom(input_tensor, shape_to), + errors::InvalidArgument("invalid input tensor shape")); } return; } else { -- GitLab From 931f6d553172ddfc9ec4a7a94ea2c6233bf33cb0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 17:39:51 -0700 Subject: [PATCH 243/906] [XLA] Redesign: handle metadata and sharding. - Add a xla.OpSharding field to the HloInstructionProto. - Metatdata handling is tested. PiperOrigin-RevId: 190553731 --- .../xla/client/xla_client/xla_builder.cc | 7 +++- .../xla/client/xla_client/xla_builder.h | 32 +++++++++++++++++++ tensorflow/compiler/xla/service/hlo.proto | 2 ++ tensorflow/compiler/xla/tests/BUILD | 3 +- .../compiler/xla/tests/hlo_metadata_test.cc | 9 +++--- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index bf91efcfd6..1b90b45bfb 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -896,8 +896,13 @@ StatusOr XlaBuilder::AddInstruction( << "Do not add XlaOp from builder " << operand.builder_->name() << " to builder " << this->name(); instr.add_operand_ids(operand.handle()); - // TODO(b/74197823): Set metadata and sharding. } + + *instr.mutable_metadata() = metadata_; + if (sharding_) { + *instr.mutable_sharding() = *sharding_; + } + instructions_.push_back(instr); XlaOp op(handle, this); diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index 22cf094512..cc33356cc1 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -85,6 +85,29 @@ class XlaBuilder { // Returns the computation name. const string& name() const { return name_; } + // Sets OpMetadata that will be added to all instructions until cleared. + // + // OpMetadata is often applied to a series of XLA HLO instructions. As a + // result, OpMetadata is set on the Computation Builder. All subsequent + // instructions generated via this Computation Builder will have the same + // OpMetadata attached until a call to ClearOpMetadata. + void SetOpMetadata(const OpMetadata& metadata) { metadata_ = metadata; } + + // Clears the HloMetadata state. + void ClearOpMetadata() { metadata_.Clear(); } + + // Sets an OpSharding that will be attached to all instructions until cleared. + void SetSharding(const OpSharding& sharding) { sharding_ = sharding; } + + // Clears the sharding. Ops will be sharded according to the default placement + // policy. + void ClearSharding() { sharding_ = tensorflow::gtl::nullopt; } + + // Returns the OpSharding that will be attached to all instructions. + const tensorflow::gtl::optional& sharding() const { + return sharding_; + } + // Sets the builder to a mode where it will die immediately when an error is // encountered, rather than producing it in a deferred fashion when Build() is // called (which is the default). @@ -776,6 +799,15 @@ class XlaBuilder { // The unique parameter numbers. tensorflow::gtl::FlatSet parameter_numbers_; + // The metadata to attach to each op. This is structured as a "modal"-like + // operation, in order to simplify client code (and not sprinkle this metadata + // throughout the TensorFlow op kernel implementations). + OpMetadata metadata_; + + // Sharding for this operator. This is structured as a "model"-like operation, + // in order to simplify client code, similar to metadata_. + tensorflow::gtl::optional sharding_; + // Mode bit that indicates whether to die when a first error is encountered. bool die_immediately_on_error_ = false; }; diff --git a/tensorflow/compiler/xla/service/hlo.proto b/tensorflow/compiler/xla/service/hlo.proto index 406feadfd4..0b446c6547 100644 --- a/tensorflow/compiler/xla/service/hlo.proto +++ b/tensorflow/compiler/xla/service/hlo.proto @@ -141,6 +141,8 @@ message HloInstructionProto { repeated int64 operand_ids = 36; repeated int64 control_predecessor_ids = 37; repeated int64 called_computation_ids = 38; + + xla.OpSharding sharding = 40; } // Serialization of HloComputation. diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 3705d6c271..5ab25f2264 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -1810,9 +1810,8 @@ tf_cc_test( deps = [ ":local_client_test_base", "//tensorflow/compiler/xla:test_helpers", - "//tensorflow/compiler/xla/client:computation_builder", "//tensorflow/compiler/xla/client:local_client", - "//tensorflow/compiler/xla/service:computation_tracker", + "//tensorflow/compiler/xla/client/xla_client:xla_builder", "//tensorflow/compiler/xla/service:cpu_plugin", "//tensorflow/compiler/xla/service:local_service", "//tensorflow/core:test_main", diff --git a/tensorflow/compiler/xla/tests/hlo_metadata_test.cc b/tensorflow/compiler/xla/tests/hlo_metadata_test.cc index eded2077fc..cf971dd61b 100644 --- a/tensorflow/compiler/xla/tests/hlo_metadata_test.cc +++ b/tensorflow/compiler/xla/tests/hlo_metadata_test.cc @@ -13,9 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/compiler/xla/client/computation_builder.h" #include "tensorflow/compiler/xla/client/local_client.h" -#include "tensorflow/compiler/xla/service/computation_tracker.h" +#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h" #include "tensorflow/compiler/xla/service/local_service.h" #include "tensorflow/compiler/xla/test_helpers.h" #include "tensorflow/compiler/xla/tests/local_client_test_base.h" @@ -30,7 +29,7 @@ class HloMetadataTest : public LocalClientTestBase { metadata_.set_op_name("my_sum_op"); } - void BuildAddComputation(ComputationBuilder* builder) { + void BuildAddComputation(XlaBuilder* builder) { auto x = builder->Parameter(0, ShapeUtil::MakeShape(F32, {}), "x"); auto y = builder->Parameter(1, ShapeUtil::MakeShape(F32, {}), "y"); builder->Add(x, y); @@ -40,7 +39,7 @@ class HloMetadataTest : public LocalClientTestBase { }; TEST_F(HloMetadataTest, MetadataPropagation) { - ComputationBuilder builder(local_client_, "add"); + XlaBuilder builder("add"); builder.SetOpMetadata(metadata_); BuildAddComputation(&builder); builder.ClearOpMetadata(); @@ -61,7 +60,7 @@ TEST_F(HloMetadataTest, MetadataPropagation) { } TEST_F(HloMetadataTest, MetadataClearing) { - ComputationBuilder builder(local_client_, "add"); + XlaBuilder builder("add"); builder.SetOpMetadata(metadata_); // Some other pretend computation here. builder.ClearOpMetadata(); -- GitLab From 0be974c423f6e5c363db2d95ed335dde4cb4e69b Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Mon, 26 Mar 2018 18:50:27 -0700 Subject: [PATCH 244/906] Finish deprecation of tf.contrib.bayesflow.{HMC,MetropolisHastings}. New home: https://github.com/tensorflow/probability/tree/master/tensorflow_probability/python/mcmc PiperOrigin-RevId: 190560180 --- tensorflow/contrib/bayesflow/BUILD | 41 - tensorflow/contrib/bayesflow/README.md | 17 + tensorflow/contrib/bayesflow/__init__.py | 8 - .../bayesflow/python/kernel_tests/hmc_test.py | 737 -------------- .../kernel_tests/metropolis_hastings_test.py | 340 ------- .../contrib/bayesflow/python/ops/hmc.py | 30 - .../contrib/bayesflow/python/ops/hmc_impl.py | 961 ------------------ .../python/ops/metropolis_hastings.py | 34 - .../python/ops/metropolis_hastings_impl.py | 527 ---------- 9 files changed, 17 insertions(+), 2678 deletions(-) create mode 100644 tensorflow/contrib/bayesflow/README.md delete mode 100644 tensorflow/contrib/bayesflow/python/kernel_tests/hmc_test.py delete mode 100644 tensorflow/contrib/bayesflow/python/kernel_tests/metropolis_hastings_test.py delete mode 100644 tensorflow/contrib/bayesflow/python/ops/hmc.py delete mode 100644 tensorflow/contrib/bayesflow/python/ops/hmc_impl.py delete mode 100644 tensorflow/contrib/bayesflow/python/ops/metropolis_hastings.py delete mode 100644 tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py diff --git a/tensorflow/contrib/bayesflow/BUILD b/tensorflow/contrib/bayesflow/BUILD index c6feec68e0..a55029b314 100644 --- a/tensorflow/contrib/bayesflow/BUILD +++ b/tensorflow/contrib/bayesflow/BUILD @@ -37,25 +37,6 @@ py_library( ], ) -cuda_py_test( - name = "metropolis_hastings_test", - size = "large", - srcs = ["python/kernel_tests/metropolis_hastings_test.py"], - additional_deps = [ - ":bayesflow_py", - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:math_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:platform_test", - "//tensorflow/python:random_ops", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - ], -) - cuda_py_test( name = "monte_carlo_test", size = "small", @@ -77,28 +58,6 @@ cuda_py_test( ], ) -cuda_py_test( - name = "hmc_test", - size = "large", - srcs = ["python/kernel_tests/hmc_test.py"], - additional_deps = [ - ":bayesflow_py", - "//third_party/py/numpy", - "//tensorflow/contrib/distributions:distributions_py", - "//tensorflow/contrib/layers:layers_py", - "//tensorflow/python/ops/distributions", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:gradients", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform_test", - "//tensorflow/python:random_seed", - ], - tags = ["nomsan"], -) - filegroup( name = "all_files", srcs = glob( diff --git a/tensorflow/contrib/bayesflow/README.md b/tensorflow/contrib/bayesflow/README.md new file mode 100644 index 0000000000..10323dc6d5 --- /dev/null +++ b/tensorflow/contrib/bayesflow/README.md @@ -0,0 +1,17 @@ +# Notice + +`tf.contrib.bayesflow` has moved! + +See new code at [github.com/tensorflow/probability]( +https://github.com/tensorflow/probability). + +Switch imports with: + +```python +# old +import tensorflow as tf +tfp = tf.contrib.bayesflow + +# new +import tensorflow_probability as tfp +``` diff --git a/tensorflow/contrib/bayesflow/__init__.py b/tensorflow/contrib/bayesflow/__init__.py index f868203826..41a8c920fc 100644 --- a/tensorflow/contrib/bayesflow/__init__.py +++ b/tensorflow/contrib/bayesflow/__init__.py @@ -21,8 +21,6 @@ from __future__ import division from __future__ import print_function # pylint: disable=unused-import,line-too-long -from tensorflow.contrib.bayesflow.python.ops import hmc -from tensorflow.contrib.bayesflow.python.ops import metropolis_hastings from tensorflow.contrib.bayesflow.python.ops import monte_carlo # pylint: enable=unused-import,line-too-long @@ -30,13 +28,7 @@ from tensorflow.python.util.all_util import remove_undocumented _allowed_symbols = [ - 'entropy', - 'hmc', - 'metropolis_hastings', 'monte_carlo', - 'special_math', - 'stochastic_variables', - 'variational_inference', ] remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/bayesflow/python/kernel_tests/hmc_test.py b/tensorflow/contrib/bayesflow/python/kernel_tests/hmc_test.py deleted file mode 100644 index dabadfc7b6..0000000000 --- a/tensorflow/contrib/bayesflow/python/kernel_tests/hmc_test.py +++ /dev/null @@ -1,737 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for Hamiltonian Monte Carlo.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections - -import numpy as np -from scipy import stats - -from tensorflow.contrib.bayesflow.python.ops import hmc -from tensorflow.contrib.bayesflow.python.ops.hmc_impl import _compute_energy_change -from tensorflow.contrib.bayesflow.python.ops.hmc_impl import _leapfrog_integrator - -from tensorflow.contrib.distributions.python.ops import independent as independent_lib -from tensorflow.python.framework import ops -from tensorflow.python.framework import random_seed -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import gen_linalg_ops -from tensorflow.python.ops import gradients_impl as gradients_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import random_ops -from tensorflow.python.ops.distributions import gamma as gamma_lib -from tensorflow.python.ops.distributions import normal as normal_lib -from tensorflow.python.platform import test -from tensorflow.python.platform import tf_logging as logging_ops - - -def _reduce_variance(x, axis=None, keepdims=False): - sample_mean = math_ops.reduce_mean(x, axis, keepdims=True) - return math_ops.reduce_mean( - math_ops.squared_difference(x, sample_mean), axis, keepdims) - - -class HMCTest(test.TestCase): - - def setUp(self): - self._shape_param = 5. - self._rate_param = 10. - - random_seed.set_random_seed(10003) - np.random.seed(10003) - - def assertAllFinite(self, x): - self.assertAllEqual(np.ones_like(x).astype(bool), np.isfinite(x)) - - def _log_gamma_log_prob(self, x, event_dims=()): - """Computes log-pdf of a log-gamma random variable. - - Args: - x: Value of the random variable. - event_dims: Dimensions not to treat as independent. - - Returns: - log_prob: The log-pdf up to a normalizing constant. - """ - return math_ops.reduce_sum(self._shape_param * x - - self._rate_param * math_ops.exp(x), - event_dims) - - def _integrator_conserves_energy(self, x, independent_chain_ndims, sess, - feed_dict=None): - step_size = array_ops.placeholder(np.float32, [], name="step_size") - hmc_lf_steps = array_ops.placeholder(np.int32, [], name="hmc_lf_steps") - - if feed_dict is None: - feed_dict = {} - feed_dict[hmc_lf_steps] = 1000 - - event_dims = math_ops.range(independent_chain_ndims, - array_ops.rank(x)) - - m = random_ops.random_normal(array_ops.shape(x)) - log_prob_0 = self._log_gamma_log_prob(x, event_dims) - grad_0 = gradients_ops.gradients(log_prob_0, x) - old_energy = -log_prob_0 + 0.5 * math_ops.reduce_sum(m**2., event_dims) - - new_m, _, log_prob_1, _ = _leapfrog_integrator( - current_momentums=[m], - target_log_prob_fn=lambda x: self._log_gamma_log_prob(x, event_dims), - current_state_parts=[x], - step_sizes=[step_size], - num_leapfrog_steps=hmc_lf_steps, - current_target_log_prob=log_prob_0, - current_grads_target_log_prob=grad_0) - new_m = new_m[0] - - new_energy = -log_prob_1 + 0.5 * math_ops.reduce_sum(new_m * new_m, - event_dims) - - x_shape = sess.run(x, feed_dict).shape - event_size = np.prod(x_shape[independent_chain_ndims:]) - feed_dict[step_size] = 0.1 / event_size - old_energy_, new_energy_ = sess.run([old_energy, new_energy], - feed_dict) - logging_ops.vlog(1, "average energy relative change: {}".format( - (1. - new_energy_ / old_energy_).mean())) - self.assertAllClose(old_energy_, new_energy_, atol=0., rtol=0.02) - - def _integrator_conserves_energy_wrapper(self, independent_chain_ndims): - """Tests the long-term energy conservation of the leapfrog integrator. - - The leapfrog integrator is symplectic, so for sufficiently small step - sizes it should be possible to run it more or less indefinitely without - the energy of the system blowing up or collapsing. - - Args: - independent_chain_ndims: Python `int` scalar representing the number of - dims associated with independent chains. - """ - with self.test_session(graph=ops.Graph()) as sess: - x_ph = array_ops.placeholder(np.float32, name="x_ph") - feed_dict = {x_ph: np.random.rand(50, 10, 2)} - self._integrator_conserves_energy(x_ph, independent_chain_ndims, - sess, feed_dict) - - def testIntegratorEnergyConservationNullShape(self): - self._integrator_conserves_energy_wrapper(0) - - def testIntegratorEnergyConservation1(self): - self._integrator_conserves_energy_wrapper(1) - - def testIntegratorEnergyConservation2(self): - self._integrator_conserves_energy_wrapper(2) - - def testIntegratorEnergyConservation3(self): - self._integrator_conserves_energy_wrapper(3) - - def testSampleChainSeedReproducibleWorksCorrectly(self): - with self.test_session(graph=ops.Graph()) as sess: - num_results = 10 - independent_chain_ndims = 1 - - def log_gamma_log_prob(x): - event_dims = math_ops.range(independent_chain_ndims, - array_ops.rank(x)) - return self._log_gamma_log_prob(x, event_dims) - - kwargs = dict( - target_log_prob_fn=log_gamma_log_prob, - current_state=np.random.rand(4, 3, 2), - step_size=0.1, - num_leapfrog_steps=2, - num_burnin_steps=150, - seed=52, - ) - - samples0, kernel_results0 = hmc.sample_chain( - **dict(list(kwargs.items()) + list(dict( - num_results=2 * num_results, - num_steps_between_results=0).items()))) - - samples1, kernel_results1 = hmc.sample_chain( - **dict(list(kwargs.items()) + list(dict( - num_results=num_results, - num_steps_between_results=1).items()))) - - [ - samples0_, - samples1_, - target_log_prob0_, - target_log_prob1_, - ] = sess.run([ - samples0, - samples1, - kernel_results0.current_target_log_prob, - kernel_results1.current_target_log_prob, - ]) - self.assertAllClose(samples0_[::2], samples1_, - atol=1e-5, rtol=1e-5) - self.assertAllClose(target_log_prob0_[::2], target_log_prob1_, - atol=1e-5, rtol=1e-5) - - def _chain_gets_correct_expectations(self, x, independent_chain_ndims, - sess, feed_dict=None): - counter = collections.Counter() - def log_gamma_log_prob(x): - counter["target_calls"] += 1 - event_dims = math_ops.range(independent_chain_ndims, - array_ops.rank(x)) - return self._log_gamma_log_prob(x, event_dims) - - num_results = array_ops.placeholder( - np.int32, [], name="num_results") - step_size = array_ops.placeholder( - np.float32, [], name="step_size") - num_leapfrog_steps = array_ops.placeholder( - np.int32, [], name="num_leapfrog_steps") - - if feed_dict is None: - feed_dict = {} - feed_dict.update({num_results: 150, - step_size: 0.05, - num_leapfrog_steps: 2}) - - samples, kernel_results = hmc.sample_chain( - num_results=num_results, - target_log_prob_fn=log_gamma_log_prob, - current_state=x, - step_size=step_size, - num_leapfrog_steps=num_leapfrog_steps, - num_burnin_steps=150, - seed=42) - - self.assertAllEqual(dict(target_calls=2), counter) - - expected_x = (math_ops.digamma(self._shape_param) - - np.log(self._rate_param)) - - expected_exp_x = self._shape_param / self._rate_param - - log_accept_ratio_, samples_, expected_x_ = sess.run( - [kernel_results.log_accept_ratio, samples, expected_x], - feed_dict) - - actual_x = samples_.mean() - actual_exp_x = np.exp(samples_).mean() - acceptance_probs = np.exp(np.minimum(log_accept_ratio_, 0.)) - - logging_ops.vlog(1, "True E[x, exp(x)]: {}\t{}".format( - expected_x_, expected_exp_x)) - logging_ops.vlog(1, "Estimated E[x, exp(x)]: {}\t{}".format( - actual_x, actual_exp_x)) - self.assertNear(actual_x, expected_x_, 2e-2) - self.assertNear(actual_exp_x, expected_exp_x, 2e-2) - self.assertAllEqual(np.ones_like(acceptance_probs, np.bool), - acceptance_probs > 0.5) - self.assertAllEqual(np.ones_like(acceptance_probs, np.bool), - acceptance_probs <= 1.) - - def _chain_gets_correct_expectations_wrapper(self, independent_chain_ndims): - with self.test_session(graph=ops.Graph()) as sess: - x_ph = array_ops.placeholder(np.float32, name="x_ph") - feed_dict = {x_ph: np.random.rand(50, 10, 2)} - self._chain_gets_correct_expectations(x_ph, independent_chain_ndims, - sess, feed_dict) - - def testHMCChainExpectationsNullShape(self): - self._chain_gets_correct_expectations_wrapper(0) - - def testHMCChainExpectations1(self): - self._chain_gets_correct_expectations_wrapper(1) - - def testHMCChainExpectations2(self): - self._chain_gets_correct_expectations_wrapper(2) - - def testKernelResultsUsingTruncatedDistribution(self): - def log_prob(x): - return array_ops.where( - x >= 0., - -x - x**2, # Non-constant gradient. - array_ops.fill(x.shape, math_ops.cast(-np.inf, x.dtype))) - # This log_prob has the property that it is likely to attract - # the flow toward, and below, zero...but for x <=0, - # log_prob(x) = -inf, which should result in rejection, as well - # as a non-finite log_prob. Thus, this distribution gives us an opportunity - # to test out the kernel results ability to correctly capture rejections due - # to finite AND non-finite reasons. - # Why use a non-constant gradient? This ensures the leapfrog integrator - # will not be exact. - - num_results = 1000 - # Large step size, will give rejections due to integration error in addition - # to rejection due to going into a region of log_prob = -inf. - step_size = 0.1 - num_leapfrog_steps = 5 - num_chains = 2 - - with self.test_session(graph=ops.Graph()) as sess: - - # Start multiple independent chains. - initial_state = ops.convert_to_tensor([0.1] * num_chains) - - states, kernel_results = hmc.sample_chain( - num_results=num_results, - target_log_prob_fn=log_prob, - current_state=initial_state, - step_size=step_size, - num_leapfrog_steps=num_leapfrog_steps, - seed=42) - - states_, kernel_results_ = sess.run([states, kernel_results]) - pstates_ = kernel_results_.proposed_state - - neg_inf_mask = np.isneginf(kernel_results_.proposed_target_log_prob) - - # First: Test that the mathematical properties of the above log prob - # function in conjunction with HMC show up as expected in kernel_results_. - - # We better have log_prob = -inf some of the time. - self.assertLess(0, neg_inf_mask.sum()) - # We better have some rejections due to something other than -inf. - self.assertLess(neg_inf_mask.sum(), (~kernel_results_.is_accepted).sum()) - # We better have accepted a decent amount, even near end of the chain. - self.assertLess( - 0.1, kernel_results_.is_accepted[int(0.9 * num_results):].mean()) - # We better not have any NaNs in states or log_prob. - # We may have some NaN in grads, which involve multiplication/addition due - # to gradient rules. This is the known "NaN grad issue with tf.where." - self.assertAllEqual(np.zeros_like(states_), - np.isnan(kernel_results_.proposed_target_log_prob)) - self.assertAllEqual(np.zeros_like(states_), - np.isnan(states_)) - # We better not have any +inf in states, grads, or log_prob. - self.assertAllEqual(np.zeros_like(states_), - np.isposinf(kernel_results_.proposed_target_log_prob)) - self.assertAllEqual( - np.zeros_like(states_), - np.isposinf(kernel_results_.proposed_grads_target_log_prob[0])) - self.assertAllEqual(np.zeros_like(states_), - np.isposinf(states_)) - - # Second: Test that kernel_results is congruent with itself and - # acceptance/rejection of states. - - # Proposed state is negative iff proposed target log prob is -inf. - np.testing.assert_array_less(pstates_[neg_inf_mask], 0.) - np.testing.assert_array_less(0., pstates_[~neg_inf_mask]) - - # Acceptance probs are zero whenever proposed state is negative. - acceptance_probs = np.exp(np.minimum( - kernel_results_.log_accept_ratio, 0.)) - self.assertAllEqual( - np.zeros_like(pstates_[neg_inf_mask]), - acceptance_probs[neg_inf_mask]) - - # The move is accepted ==> state = proposed state. - self.assertAllEqual( - states_[kernel_results_.is_accepted], - pstates_[kernel_results_.is_accepted], - ) - # The move was rejected <==> state[t] == state[t - 1]. - for t in range(1, num_results): - for i in range(num_chains): - if kernel_results_.is_accepted[t, i]: - self.assertNotEqual(states_[t, i], states_[t - 1, i]) - else: - self.assertEqual(states_[t, i], states_[t - 1, i]) - - def _kernel_leaves_target_invariant(self, initial_draws, - independent_chain_ndims, - sess, feed_dict=None): - def log_gamma_log_prob(x): - event_dims = math_ops.range(independent_chain_ndims, array_ops.rank(x)) - return self._log_gamma_log_prob(x, event_dims) - - def fake_log_prob(x): - """Cooled version of the target distribution.""" - return 1.1 * log_gamma_log_prob(x) - - step_size = array_ops.placeholder(np.float32, [], name="step_size") - - if feed_dict is None: - feed_dict = {} - - feed_dict[step_size] = 0.4 - - sample, kernel_results = hmc.kernel( - target_log_prob_fn=log_gamma_log_prob, - current_state=initial_draws, - step_size=step_size, - num_leapfrog_steps=5, - seed=43) - - bad_sample, bad_kernel_results = hmc.kernel( - target_log_prob_fn=fake_log_prob, - current_state=initial_draws, - step_size=step_size, - num_leapfrog_steps=5, - seed=44) - - [ - log_accept_ratio_, - bad_log_accept_ratio_, - initial_draws_, - updated_draws_, - fake_draws_, - ] = sess.run([ - kernel_results.log_accept_ratio, - bad_kernel_results.log_accept_ratio, - initial_draws, - sample, - bad_sample, - ], feed_dict) - - # Confirm step size is small enough that we usually accept. - acceptance_probs = np.exp(np.minimum(log_accept_ratio_, 0.)) - bad_acceptance_probs = np.exp(np.minimum(bad_log_accept_ratio_, 0.)) - self.assertGreater(acceptance_probs.mean(), 0.5) - self.assertGreater(bad_acceptance_probs.mean(), 0.5) - - # Confirm step size is large enough that we sometimes reject. - self.assertLess(acceptance_probs.mean(), 0.99) - self.assertLess(bad_acceptance_probs.mean(), 0.99) - - _, ks_p_value_true = stats.ks_2samp(initial_draws_.flatten(), - updated_draws_.flatten()) - _, ks_p_value_fake = stats.ks_2samp(initial_draws_.flatten(), - fake_draws_.flatten()) - - logging_ops.vlog(1, "acceptance rate for true target: {}".format( - acceptance_probs.mean())) - logging_ops.vlog(1, "acceptance rate for fake target: {}".format( - bad_acceptance_probs.mean())) - logging_ops.vlog(1, "K-S p-value for true target: {}".format( - ks_p_value_true)) - logging_ops.vlog(1, "K-S p-value for fake target: {}".format( - ks_p_value_fake)) - # Make sure that the MCMC update hasn't changed the empirical CDF much. - self.assertGreater(ks_p_value_true, 1e-3) - # Confirm that targeting the wrong distribution does - # significantly change the empirical CDF. - self.assertLess(ks_p_value_fake, 1e-6) - - def _kernel_leaves_target_invariant_wrapper(self, independent_chain_ndims): - """Tests that the kernel leaves the target distribution invariant. - - Draws some independent samples from the target distribution, - applies an iteration of the MCMC kernel, then runs a - Kolmogorov-Smirnov test to determine if the distribution of the - MCMC-updated samples has changed. - - We also confirm that running the kernel with a different log-pdf - does change the target distribution. (And that we can detect that.) - - Args: - independent_chain_ndims: Python `int` scalar representing the number of - dims associated with independent chains. - """ - with self.test_session(graph=ops.Graph()) as sess: - initial_draws = np.log(np.random.gamma(self._shape_param, - size=[50000, 2, 2])) - initial_draws -= np.log(self._rate_param) - x_ph = array_ops.placeholder(np.float32, name="x_ph") - - feed_dict = {x_ph: initial_draws} - - self._kernel_leaves_target_invariant(x_ph, independent_chain_ndims, - sess, feed_dict) - - def testKernelLeavesTargetInvariant1(self): - self._kernel_leaves_target_invariant_wrapper(1) - - def testKernelLeavesTargetInvariant2(self): - self._kernel_leaves_target_invariant_wrapper(2) - - def testKernelLeavesTargetInvariant3(self): - self._kernel_leaves_target_invariant_wrapper(3) - - def testNanRejection(self): - """Tests that an update that yields NaN potentials gets rejected. - - We run HMC with a target distribution that returns NaN - log-likelihoods if any element of x < 0, and unit-scale - exponential log-likelihoods otherwise. The exponential potential - pushes x towards 0, ensuring that any reasonably large update will - push us over the edge into NaN territory. - """ - def _unbounded_exponential_log_prob(x): - """An exponential distribution with log-likelihood NaN for x < 0.""" - per_element_potentials = array_ops.where( - x < 0., - array_ops.fill(array_ops.shape(x), x.dtype.as_numpy_dtype(np.nan)), - -x) - return math_ops.reduce_sum(per_element_potentials) - - with self.test_session(graph=ops.Graph()) as sess: - initial_x = math_ops.linspace(0.01, 5, 10) - updated_x, kernel_results = hmc.kernel( - target_log_prob_fn=_unbounded_exponential_log_prob, - current_state=initial_x, - step_size=2., - num_leapfrog_steps=5, - seed=46) - initial_x_, updated_x_, log_accept_ratio_ = sess.run( - [initial_x, updated_x, kernel_results.log_accept_ratio]) - acceptance_probs = np.exp(np.minimum(log_accept_ratio_, 0.)) - - logging_ops.vlog(1, "initial_x = {}".format(initial_x_)) - logging_ops.vlog(1, "updated_x = {}".format(updated_x_)) - logging_ops.vlog(1, "log_accept_ratio = {}".format(log_accept_ratio_)) - - self.assertAllEqual(initial_x_, updated_x_) - self.assertEqual(acceptance_probs, 0.) - - def testNanFromGradsDontPropagate(self): - """Test that update with NaN gradients does not cause NaN in results.""" - def _nan_log_prob_with_nan_gradient(x): - return np.nan * math_ops.reduce_sum(x) - - with self.test_session(graph=ops.Graph()) as sess: - initial_x = math_ops.linspace(0.01, 5, 10) - updated_x, kernel_results = hmc.kernel( - target_log_prob_fn=_nan_log_prob_with_nan_gradient, - current_state=initial_x, - step_size=2., - num_leapfrog_steps=5, - seed=47) - initial_x_, updated_x_, log_accept_ratio_ = sess.run( - [initial_x, updated_x, kernel_results.log_accept_ratio]) - acceptance_probs = np.exp(np.minimum(log_accept_ratio_, 0.)) - - logging_ops.vlog(1, "initial_x = {}".format(initial_x_)) - logging_ops.vlog(1, "updated_x = {}".format(updated_x_)) - logging_ops.vlog(1, "log_accept_ratio = {}".format(log_accept_ratio_)) - - self.assertAllEqual(initial_x_, updated_x_) - self.assertEqual(acceptance_probs, 0.) - - self.assertAllFinite( - gradients_ops.gradients(updated_x, initial_x)[0].eval()) - self.assertAllEqual([True], [g is None for g in gradients_ops.gradients( - kernel_results.proposed_grads_target_log_prob, initial_x)]) - self.assertAllEqual([False], [g is None for g in gradients_ops.gradients( - kernel_results.proposed_grads_target_log_prob, - kernel_results.proposed_state)]) - - # Gradients of the acceptance probs and new log prob are not finite. - # self.assertAllFinite( - # gradients_ops.gradients(acceptance_probs, initial_x)[0].eval()) - # self.assertAllFinite( - # gradients_ops.gradients(new_log_prob, initial_x)[0].eval()) - - def _testChainWorksDtype(self, dtype): - with self.test_session(graph=ops.Graph()) as sess: - states, kernel_results = hmc.sample_chain( - num_results=10, - target_log_prob_fn=lambda x: -math_ops.reduce_sum(x**2., axis=-1), - current_state=np.zeros(5).astype(dtype), - step_size=0.01, - num_leapfrog_steps=10, - seed=48) - states_, log_accept_ratio_ = sess.run( - [states, kernel_results.log_accept_ratio]) - self.assertEqual(dtype, states_.dtype) - self.assertEqual(dtype, log_accept_ratio_.dtype) - - def testChainWorksIn64Bit(self): - self._testChainWorksDtype(np.float64) - - def testChainWorksIn16Bit(self): - self._testChainWorksDtype(np.float16) - - def testChainWorksCorrelatedMultivariate(self): - dtype = np.float32 - true_mean = dtype([0, 0]) - true_cov = dtype([[1, 0.5], - [0.5, 1]]) - num_results = 2000 - counter = collections.Counter() - with self.test_session(graph=ops.Graph()) as sess: - def target_log_prob(x, y): - counter["target_calls"] += 1 - # Corresponds to unnormalized MVN. - # z = matmul(inv(chol(true_cov)), [x, y] - true_mean) - z = array_ops.stack([x, y], axis=-1) - true_mean - z = array_ops.squeeze( - gen_linalg_ops.matrix_triangular_solve( - np.linalg.cholesky(true_cov), - z[..., array_ops.newaxis]), - axis=-1) - return -0.5 * math_ops.reduce_sum(z**2., axis=-1) - states, _ = hmc.sample_chain( - num_results=num_results, - target_log_prob_fn=target_log_prob, - current_state=[dtype(-2), dtype(2)], - step_size=[0.5, 0.5], - num_leapfrog_steps=2, - num_burnin_steps=200, - num_steps_between_results=1, - seed=54) - self.assertAllEqual(dict(target_calls=2), counter) - states = array_ops.stack(states, axis=-1) - self.assertEqual(num_results, states.shape[0].value) - sample_mean = math_ops.reduce_mean(states, axis=0) - x = states - sample_mean - sample_cov = math_ops.matmul(x, x, transpose_a=True) / dtype(num_results) - [sample_mean_, sample_cov_] = sess.run([ - sample_mean, sample_cov]) - self.assertAllClose(true_mean, sample_mean_, - atol=0.05, rtol=0.) - self.assertAllClose(true_cov, sample_cov_, - atol=0., rtol=0.1) - - -class _EnergyComputationTest(object): - - def testHandlesNanFromPotential(self): - with self.test_session(graph=ops.Graph()) as sess: - x = [1, np.inf, -np.inf, np.nan] - target_log_prob, proposed_target_log_prob = [ - self.dtype(x.flatten()) for x in np.meshgrid(x, x)] - num_chains = len(target_log_prob) - dummy_momentums = [-1, 1] - momentums = [self.dtype([dummy_momentums] * num_chains)] - proposed_momentums = [self.dtype([dummy_momentums] * num_chains)] - - target_log_prob = ops.convert_to_tensor(target_log_prob) - momentums = [ops.convert_to_tensor(momentums[0])] - proposed_target_log_prob = ops.convert_to_tensor(proposed_target_log_prob) - proposed_momentums = [ops.convert_to_tensor(proposed_momentums[0])] - - energy = _compute_energy_change( - target_log_prob, - momentums, - proposed_target_log_prob, - proposed_momentums, - independent_chain_ndims=1) - grads = gradients_ops.gradients(energy, momentums) - - [actual_energy, grads_] = sess.run([energy, grads]) - - # Ensure energy is `inf` (note: that's positive inf) in weird cases and - # finite otherwise. - expected_energy = self.dtype([0] + [np.inf]*(num_chains - 1)) - self.assertAllEqual(expected_energy, actual_energy) - - # Ensure gradient is finite. - self.assertAllEqual(np.ones_like(grads_).astype(np.bool), - np.isfinite(grads_)) - - def testHandlesNanFromKinetic(self): - with self.test_session(graph=ops.Graph()) as sess: - x = [1, np.inf, -np.inf, np.nan] - momentums, proposed_momentums = [ - [np.reshape(self.dtype(x), [-1, 1])] - for x in np.meshgrid(x, x)] - num_chains = len(momentums[0]) - target_log_prob = np.ones(num_chains, self.dtype) - proposed_target_log_prob = np.ones(num_chains, self.dtype) - - target_log_prob = ops.convert_to_tensor(target_log_prob) - momentums = [ops.convert_to_tensor(momentums[0])] - proposed_target_log_prob = ops.convert_to_tensor(proposed_target_log_prob) - proposed_momentums = [ops.convert_to_tensor(proposed_momentums[0])] - - energy = _compute_energy_change( - target_log_prob, - momentums, - proposed_target_log_prob, - proposed_momentums, - independent_chain_ndims=1) - grads = gradients_ops.gradients(energy, momentums) - - [actual_energy, grads_] = sess.run([energy, grads]) - - # Ensure energy is `inf` (note: that's positive inf) in weird cases and - # finite otherwise. - expected_energy = self.dtype([0] + [np.inf]*(num_chains - 1)) - self.assertAllEqual(expected_energy, actual_energy) - - # Ensure gradient is finite. - g = grads_[0].reshape([len(x), len(x)])[:, 0] - self.assertAllEqual(np.ones_like(g).astype(np.bool), np.isfinite(g)) - - # The remaining gradients are nan because the momentum was itself nan or - # inf. - g = grads_[0].reshape([len(x), len(x)])[:, 1:] - self.assertAllEqual(np.ones_like(g).astype(np.bool), np.isnan(g)) - - -class EnergyComputationTest16(test.TestCase, _EnergyComputationTest): - dtype = np.float16 - - -class EnergyComputationTest32(test.TestCase, _EnergyComputationTest): - dtype = np.float32 - - -class EnergyComputationTest64(test.TestCase, _EnergyComputationTest): - dtype = np.float64 - - -class _HMCHandlesLists(object): - - def testStateParts(self): - with self.test_session(graph=ops.Graph()) as sess: - dist_x = normal_lib.Normal(loc=self.dtype(0), scale=self.dtype(1)) - dist_y = independent_lib.Independent( - gamma_lib.Gamma(concentration=self.dtype([1, 2]), - rate=self.dtype([0.5, 0.75])), - reinterpreted_batch_ndims=1) - def target_log_prob(x, y): - return dist_x.log_prob(x) + dist_y.log_prob(y) - x0 = [dist_x.sample(seed=1), dist_y.sample(seed=2)] - samples, _ = hmc.sample_chain( - num_results=int(2e3), - target_log_prob_fn=target_log_prob, - current_state=x0, - step_size=0.85, - num_leapfrog_steps=3, - num_burnin_steps=int(250), - seed=49) - actual_means = [math_ops.reduce_mean(s, axis=0) for s in samples] - actual_vars = [_reduce_variance(s, axis=0) for s in samples] - expected_means = [dist_x.mean(), dist_y.mean()] - expected_vars = [dist_x.variance(), dist_y.variance()] - [ - actual_means_, - actual_vars_, - expected_means_, - expected_vars_, - ] = sess.run([ - actual_means, - actual_vars, - expected_means, - expected_vars, - ]) - self.assertAllClose(expected_means_, actual_means_, atol=0.05, rtol=0.16) - self.assertAllClose(expected_vars_, actual_vars_, atol=0., rtol=0.25) - - -class HMCHandlesLists32(_HMCHandlesLists, test.TestCase): - dtype = np.float32 - - -class HMCHandlesLists64(_HMCHandlesLists, test.TestCase): - dtype = np.float64 - - -if __name__ == "__main__": - test.main() diff --git a/tensorflow/contrib/bayesflow/python/kernel_tests/metropolis_hastings_test.py b/tensorflow/contrib/bayesflow/python/kernel_tests/metropolis_hastings_test.py deleted file mode 100644 index f508e5b114..0000000000 --- a/tensorflow/contrib/bayesflow/python/kernel_tests/metropolis_hastings_test.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for Metropolis-Hastings.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np - -from tensorflow.contrib.bayesflow.python.ops import metropolis_hastings_impl as mh -from tensorflow.contrib.distributions.python.ops import mvn_tril as mvn_tril_lib -from tensorflow.python.framework import constant_op -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import init_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import random_ops -from tensorflow.python.ops import variable_scope -from tensorflow.python.ops import variables -from tensorflow.python.ops.distributions import normal as normal_lib -from tensorflow.python.platform import test - - -class MetropolisHastingsTest(test.TestCase): - - def testKernelStateTensor(self): - """Test that transition kernel works with tensor input to `state`.""" - loc = variable_scope.get_variable("loc", initializer=0.) - - def target_log_prob_fn(loc): - return normal_lib.Normal(loc=0.0, scale=0.1).log_prob(loc) - - new_state, _ = mh.kernel( - target_log_prob_fn=target_log_prob_fn, - proposal_fn=mh.proposal_normal(scale=0.05), - current_state=loc, - seed=231251) - loc_update = loc.assign(new_state) - - init = variables.initialize_all_variables() - with self.test_session() as sess: - sess.run(init) - loc_samples = [] - for _ in range(2500): - loc_sample = sess.run(loc_update) - loc_samples.append(loc_sample) - loc_samples = loc_samples[500:] # drop samples for burn-in - - self.assertAllClose(np.mean(loc_samples), 0.0, rtol=1e-5, atol=1e-1) - self.assertAllClose(np.std(loc_samples), 0.1, rtol=1e-5, atol=1e-1) - - def testKernelStateList(self): - """Test that transition kernel works with list input to `state`.""" - num_chains = 2 - loc_one = variable_scope.get_variable( - "loc_one", [num_chains], - initializer=init_ops.zeros_initializer()) - loc_two = variable_scope.get_variable( - "loc_two", [num_chains], initializer=init_ops.zeros_initializer()) - - def target_log_prob_fn(loc_one, loc_two): - loc = array_ops.stack([loc_one, loc_two]) - log_prob = mvn_tril_lib.MultivariateNormalTriL( - loc=constant_op.constant([0., 0.]), - scale_tril=constant_op.constant([[0.1, 0.1], [0.0, 0.1]])).log_prob( - loc) - return math_ops.reduce_sum(log_prob, 0) - - def proposal_fn(loc_one, loc_two): - loc_one_proposal = mh.proposal_normal(scale=0.05) - loc_two_proposal = mh.proposal_normal(scale=0.05) - loc_one_sample, _ = loc_one_proposal(loc_one) - loc_two_sample, _ = loc_two_proposal(loc_two) - return [loc_one_sample, loc_two_sample], None - - new_state, _ = mh.kernel( - target_log_prob_fn=target_log_prob_fn, - proposal_fn=proposal_fn, - current_state=[loc_one, loc_two], - seed=12415) - loc_one_update = loc_one.assign(new_state[0]) - loc_two_update = loc_two.assign(new_state[1]) - - init = variables.initialize_all_variables() - with self.test_session() as sess: - sess.run(init) - loc_one_samples = [] - loc_two_samples = [] - for _ in range(10000): - loc_one_sample, loc_two_sample = sess.run( - [loc_one_update, loc_two_update]) - loc_one_samples.append(loc_one_sample) - loc_two_samples.append(loc_two_sample) - - loc_one_samples = np.array(loc_one_samples) - loc_two_samples = np.array(loc_two_samples) - loc_one_samples = loc_one_samples[1000:] # drop samples for burn-in - loc_two_samples = loc_two_samples[1000:] # drop samples for burn-in - - self.assertAllClose(np.mean(loc_one_samples, 0), - np.array([0.] * num_chains), - rtol=1e-5, atol=1e-1) - self.assertAllClose(np.mean(loc_two_samples, 0), - np.array([0.] * num_chains), - rtol=1e-5, atol=1e-1) - self.assertAllClose(np.std(loc_one_samples, 0), - np.array([0.1] * num_chains), - rtol=1e-5, atol=1e-1) - self.assertAllClose(np.std(loc_two_samples, 0), - np.array([0.1] * num_chains), - rtol=1e-5, atol=1e-1) - - def testKernelResultsUsingTruncatedDistribution(self): - def log_prob(x): - return array_ops.where( - x >= 0., - -x - x**2, - array_ops.fill(x.shape, math_ops.cast(-np.inf, x.dtype))) - # The truncated distribution has the property that it is likely to attract - # the flow toward, and below, zero...but for x <=0, - # log_prob(x) = -inf, which should result in rejection, as well - # as a non-finite log_prob. Thus, this distribution gives us an opportunity - # to test out the kernel results ability to correctly capture rejections due - # to finite AND non-finite reasons. - - num_results = 1000 - # Large step size, will give rejections due to going into a region of - # log_prob = -inf. - step_size = 0.3 - num_chains = 2 - - with self.test_session(graph=ops.Graph()) as sess: - - # Start multiple independent chains. - initial_state = ops.convert_to_tensor([0.1] * num_chains) - - states = [] - is_accepted = [] - proposed_states = [] - current_state = initial_state - for _ in range(num_results): - current_state, kernel_results = mh.kernel( - target_log_prob_fn=log_prob, - proposal_fn=mh.proposal_uniform(step_size=step_size), - current_state=current_state, - seed=42) - states.append(current_state) - proposed_states.append(kernel_results.proposed_state) - is_accepted.append(kernel_results.is_accepted) - - states = array_ops.stack(states) - proposed_states = array_ops.stack(proposed_states) - is_accepted = array_ops.stack(is_accepted) - states_, pstates_, is_accepted_ = sess.run( - [states, proposed_states, is_accepted]) - - # We better have accepted a decent amount, even near end of the chain. - self.assertLess( - 0.1, is_accepted_[int(0.9 * num_results):].mean()) - # We better not have any NaNs in states. - self.assertAllEqual(np.zeros_like(states_), - np.isnan(states_)) - # We better not have any +inf in states. - self.assertAllEqual(np.zeros_like(states_), - np.isposinf(states_)) - - # The move is accepted ==> state = proposed state. - self.assertAllEqual( - states_[is_accepted_], - pstates_[is_accepted_], - ) - - # The move was rejected <==> state[t] == state[t - 1]. - for t in range(1, num_results): - for i in range(num_chains): - if is_accepted_[t, i]: - self.assertNotEqual(states_[t, i], states_[t - 1, i]) - else: - self.assertEqual(states_[t, i], states_[t - 1, i]) - - def testDensityIncreasingStepAccepted(self): - """Tests that if a transition increases density, it is always accepted.""" - target_log_density = lambda x: - x * x - state = variable_scope.get_variable("state", initializer=10.) - state_log_density = variable_scope.get_variable( - "state_log_density", - initializer=target_log_density(state.initialized_value())) - log_accept_ratio = variable_scope.get_variable( - "log_accept_ratio", initializer=0.) - - get_next_proposal = lambda x: (x - 1., None) - step = mh.evolve(state, state_log_density, log_accept_ratio, - target_log_density, get_next_proposal, seed=1234) - init = variables.initialize_all_variables() - with self.test_session() as sess: - sess.run(init) - for j in range(9): - sess.run(step) - sample = sess.run(state) - sample_log_density = sess.run(state_log_density) - self.assertAlmostEqual(sample, 9 - j) - self.assertAlmostEqual(sample_log_density, - (9 - j) * (9 - j)) - - def testSampleProperties(self): - """Tests that the samples converge to the target distribution.""" - - def target_log_density(x): - """Log-density corresponding to a normal distribution with mean = 4.""" - return - (x - 2.0) * (x - 2.0) * 0.5 - - # Use the uniform random walker to generate proposals. - proposal_fn = mh.proposal_uniform( - step_size=1.0, seed=1234) - - state = variable_scope.get_variable("state", initializer=0.0) - state_log_density = variable_scope.get_variable( - "state_log_density", - initializer=target_log_density(state.initialized_value())) - log_accept_ratio = variable_scope.get_variable( - "log_accept_ratio", initializer=0.) - - # Random walk MCMC converges slowly so need to put in enough iterations. - num_iterations = 5000 - step = mh.evolve(state, state_log_density, log_accept_ratio, - target_log_density, proposal_fn, seed=4321) - - init = variables.global_variables_initializer() - - sample_sum, sample_sq_sum = 0.0, 0.0 - with self.test_session() as sess: - sess.run(init) - for _ in np.arange(num_iterations): - # Allow for the mixing of the chain and discard these samples. - sess.run(step) - for _ in np.arange(num_iterations): - sess.run(step) - sample = sess.run(state) - sample_sum += sample - sample_sq_sum += sample * sample - - sample_mean = sample_sum / num_iterations - sample_variance = sample_sq_sum / num_iterations - sample_mean * sample_mean - # The samples have large autocorrelation which reduces the effective sample - # size. - self.assertAlmostEqual(sample_mean, 2.0, delta=0.1) - self.assertAlmostEqual(sample_variance, 1.0, delta=0.1) - - def testProposalNormal(self): - """Tests that the normal proposals are correctly distributed.""" - - initial_points = array_ops.ones([10000], dtype=dtypes.float32) - proposal_fn = mh.proposal_normal( - scale=2.0, seed=1234) - proposal_points, _ = proposal_fn(initial_points) - - with self.test_session() as sess: - sample = sess.run(proposal_points) - - # It is expected that the elements in proposal_points have the same mean as - # initial_points and have the standard deviation that was supplied to the - # proposal scheme. - self.assertAlmostEqual(np.mean(sample), 1.0, delta=0.1) - self.assertAlmostEqual(np.std(sample), 2.0, delta=0.1) - - def testDocstringExample(self): - """Tests the simplified docstring example with multiple chains.""" - - n = 2 # dimension of the problem - - # Generate 300 initial values randomly. Each of these would be an - # independent starting point for a Markov chain. - state = variable_scope.get_variable( - "state", initializer=random_ops.random_normal( - [300, n], mean=3.0, dtype=dtypes.float32, seed=42)) - - # Computes the log(p(x)) for the unit normal density and ignores the - # normalization constant. - def log_density(x): - return - math_ops.reduce_sum(x * x, reduction_indices=-1) / 2.0 - - # Initial log-density value - state_log_density = variable_scope.get_variable( - "state_log_density", - initializer=log_density(state.initialized_value())) - - # A variable to store the log_acceptance_ratio: - log_acceptance_ratio = variable_scope.get_variable( - "log_acceptance_ratio", - initializer=array_ops.zeros([300], dtype=dtypes.float32)) - - # Generates random proposals by moving each coordinate uniformly and - # independently in a box of size 2 centered around the current value. - # Returns the new point and also the log of the Hastings ratio (the - # ratio of the probability of going from the proposal to origin and the - # probability of the reverse transition). When this ratio is 1, the value - # may be omitted and replaced by None. - def random_proposal(x): - return (x + random_ops.random_uniform( - array_ops.shape(x), minval=-1, maxval=1, - dtype=x.dtype, seed=12)), None - - # Create the op to propagate the chain for 100 steps. - stepper = mh.evolve( - state, state_log_density, log_acceptance_ratio, - log_density, random_proposal, n_steps=100, seed=123) - init = variables.initialize_all_variables() - with self.test_session() as sess: - sess.run(init) - # Run the chains for a total of 1000 steps. - for _ in range(10): - sess.run(stepper) - samples = sess.run(state) - covariance = np.eye(n) - # Verify that the estimated mean and covariance are close to the true - # values. - self.assertAlmostEqual( - np.max(np.abs(np.mean(samples, 0) - - np.zeros(n))), 0, - delta=0.1) - self.assertAlmostEqual( - np.max(np.abs(np.reshape(np.cov(samples, rowvar=False), [n**2]) - - np.reshape(covariance, [n**2]))), 0, - delta=0.2) - -if __name__ == "__main__": - test.main() diff --git a/tensorflow/contrib/bayesflow/python/ops/hmc.py b/tensorflow/contrib/bayesflow/python/ops/hmc.py deleted file mode 100644 index c8a5a195d3..0000000000 --- a/tensorflow/contrib/bayesflow/python/ops/hmc.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Hamiltonian Monte Carlo, a gradient-based MCMC algorithm.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -# go/tf-wildcard-import -from tensorflow.contrib.bayesflow.python.ops.hmc_impl import * # pylint: disable=wildcard-import,unused-wildcard-import,g-importing-member -from tensorflow.python.util import all_util - -_allowed_symbols = [ - "sample_chain", - "kernel", -] - -all_util.remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/bayesflow/python/ops/hmc_impl.py b/tensorflow/contrib/bayesflow/python/ops/hmc_impl.py deleted file mode 100644 index 66afcc7497..0000000000 --- a/tensorflow/contrib/bayesflow/python/ops/hmc_impl.py +++ /dev/null @@ -1,961 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Hamiltonian Monte Carlo, a gradient-based MCMC algorithm. - -@@sample_chain -@@kernel -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections -import numpy as np - -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import functional_ops -from tensorflow.python.ops import gradients_impl as gradients_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import random_ops -from tensorflow.python.ops.distributions import util as distributions_util - -__all__ = [ - "sample_chain", - "kernel", -] - - -KernelResults = collections.namedtuple( - "KernelResults", - [ - "log_accept_ratio", - "current_grads_target_log_prob", # "Current result" means "accepted". - "current_target_log_prob", # "Current result" means "accepted". - "is_accepted", - "proposed_grads_target_log_prob", - "proposed_state", - "proposed_target_log_prob", - ]) - - -def _make_dummy_kernel_results( - dummy_state, - dummy_target_log_prob, - dummy_grads_target_log_prob): - return KernelResults( - log_accept_ratio=dummy_target_log_prob, - current_grads_target_log_prob=dummy_grads_target_log_prob, - current_target_log_prob=dummy_target_log_prob, - is_accepted=array_ops.ones_like(dummy_target_log_prob, dtypes.bool), - proposed_grads_target_log_prob=dummy_grads_target_log_prob, - proposed_state=dummy_state, - proposed_target_log_prob=dummy_target_log_prob, - ) - - -def sample_chain( - num_results, - target_log_prob_fn, - current_state, - step_size, - num_leapfrog_steps, - num_burnin_steps=0, - num_steps_between_results=0, - seed=None, - current_target_log_prob=None, - current_grads_target_log_prob=None, - name=None): - """Runs multiple iterations of one or more Hamiltonian Monte Carlo chains. - - Hamiltonian Monte Carlo (HMC) is a Markov chain Monte Carlo (MCMC) algorithm - that takes a series of gradient-informed steps to produce a Metropolis - proposal. This function samples from an HMC Markov chain at `current_state` - and whose stationary distribution has log-unnormalized-density - `target_log_prob_fn()`. - - This function samples from multiple chains in parallel. It assumes that the - the leftmost dimensions of (each) `current_state` (part) index an independent - chain. The function `target_log_prob_fn()` sums log-probabilities across - event dimensions (i.e., current state (part) rightmost dimensions). Each - element of the output of `target_log_prob_fn()` represents the (possibly - unnormalized) log-probability of the joint distribution over (all) the current - state (parts). - - The `current_state` can be represented as a single `Tensor` or a `list` of - `Tensors` which collectively represent the current state. When specifying a - `list`, one must also specify a list of `step_size`s. - - Note: `target_log_prob_fn` is called exactly twice. - - Since HMC states are correlated, it is sometimes desirable to produce - additional intermediate states, and then discard them, ending up with a set of - states with decreased autocorrelation. See [1]. Such "thinning" is made - possible by setting `num_steps_between_results > 0`. The chain then takes - `num_steps_between_results` extra steps between the steps that make it into - the results. The extra steps are never materialized (in calls to `sess.run`), - and thus do not increase memory requirements. - - [1]: "Statistically efficient thinning of a Markov chain sampler." - Art B. Owen. April 2017. - http://statweb.stanford.edu/~owen/reports/bestthinning.pdf - - #### Examples: - - ##### Sample from a diagonal-variance Gaussian. - - ```python - tfd = tf.contrib.distributions - - def make_likelihood(true_variances): - return tfd.MultivariateNormalDiag( - scale_diag=tf.sqrt(true_variances)) - - dims = 10 - dtype = np.float32 - true_variances = tf.linspace(dtype(1), dtype(3), dims) - likelihood = make_likelihood(true_variances) - - states, kernel_results = hmc.sample_chain( - num_results=1000, - target_log_prob_fn=likelihood.log_prob, - current_state=tf.zeros(dims), - step_size=0.5, - num_leapfrog_steps=2, - num_burnin_steps=500) - - # Compute sample stats. - sample_mean = tf.reduce_mean(states, axis=0) - sample_var = tf.reduce_mean( - tf.squared_difference(states, sample_mean), - axis=0) - ``` - - ##### Sampling from factor-analysis posteriors with known factors. - - I.e., - - ```none - for i=1..n: - w[i] ~ Normal(0, eye(d)) # prior - x[i] ~ Normal(loc=matmul(w[i], F)) # likelihood - ``` - - where `F` denotes factors. - - ```python - tfd = tf.contrib.distributions - - def make_prior(dims, dtype): - return tfd.MultivariateNormalDiag( - loc=tf.zeros(dims, dtype)) - - def make_likelihood(weights, factors): - return tfd.MultivariateNormalDiag( - loc=tf.tensordot(weights, factors, axes=[[0], [-1]])) - - # Setup data. - num_weights = 10 - num_factors = 4 - num_chains = 100 - dtype = np.float32 - - prior = make_prior(num_weights, dtype) - weights = prior.sample(num_chains) - factors = np.random.randn(num_factors, num_weights).astype(dtype) - x = make_likelihood(weights, factors).sample(num_chains) - - def target_log_prob(w): - # Target joint is: `f(w) = p(w, x | factors)`. - return prior.log_prob(w) + make_likelihood(w, factors).log_prob(x) - - # Get `num_results` samples from `num_chains` independent chains. - chains_states, kernels_results = hmc.sample_chain( - num_results=1000, - target_log_prob_fn=target_log_prob, - current_state=tf.zeros([num_chains, dims], dtype), - step_size=0.1, - num_leapfrog_steps=2, - num_burnin_steps=500) - - # Compute sample stats. - sample_mean = tf.reduce_mean(chains_states, axis=[0, 1]) - sample_var = tf.reduce_mean( - tf.squared_difference(chains_states, sample_mean), - axis=[0, 1]) - ``` - - Args: - num_results: Integer number of Markov chain draws. - target_log_prob_fn: Python callable which takes an argument like - `current_state` (or `*current_state` if it's a list) and returns its - (possibly unnormalized) log-density under the target distribution. - current_state: `Tensor` or Python `list` of `Tensor`s representing the - current state(s) of the Markov chain(s). The first `r` dimensions index - independent chains, `r = tf.rank(target_log_prob_fn(*current_state))`. - step_size: `Tensor` or Python `list` of `Tensor`s representing the step size - for the leapfrog integrator. Must broadcast with the shape of - `current_state`. Larger step sizes lead to faster progress, but too-large - step sizes make rejection exponentially more likely. When possible, it's - often helpful to match per-variable step sizes to the standard deviations - of the target distribution in each variable. - num_leapfrog_steps: Integer number of steps to run the leapfrog integrator - for. Total progress per HMC step is roughly proportional to `step_size * - num_leapfrog_steps`. - num_burnin_steps: Integer number of chain steps to take before starting to - collect results. - Default value: 0 (i.e., no burn-in). - num_steps_between_results: Integer number of chain steps between collecting - a result. Only one out of every `num_steps_between_samples + 1` steps is - included in the returned results. The number of returned chain states is - still equal to `num_results`. Default value: 0 (i.e., no thinning). - seed: Python integer to seed the random number generator. - current_target_log_prob: (Optional) `Tensor` representing the value of - `target_log_prob_fn` at the `current_state`. The only reason to specify - this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - current_grads_target_log_prob: (Optional) Python list of `Tensor`s - representing gradient of `target_log_prob` at the `current_state` and wrt - the `current_state`. Must have same shape as `current_state`. The only - reason to specify this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - name: Python `str` name prefixed to Ops created by this function. - Default value: `None` (i.e., "hmc_sample_chain"). - - Returns: - next_states: Tensor or Python list of `Tensor`s representing the - state(s) of the Markov chain(s) at each result step. Has same shape as - input `current_state` but with a prepended `num_results`-size dimension. - kernel_results: `collections.namedtuple` of internal calculations used to - advance the chain. - """ - with ops.name_scope( - name, "hmc_sample_chain", - [num_results, current_state, step_size, num_leapfrog_steps, - num_burnin_steps, num_steps_between_results, seed, - current_target_log_prob, current_grads_target_log_prob]): - with ops.name_scope("initialize"): - [ - current_state, - step_size, - current_target_log_prob, - current_grads_target_log_prob, - ] = _prepare_args( - target_log_prob_fn, - current_state, - step_size, - current_target_log_prob, - current_grads_target_log_prob) - num_results = ops.convert_to_tensor( - num_results, - dtype=dtypes.int32, - name="num_results") - num_leapfrog_steps = ops.convert_to_tensor( - num_leapfrog_steps, - dtype=dtypes.int32, - name="num_leapfrog_steps") - num_burnin_steps = ops.convert_to_tensor( - num_burnin_steps, - dtype=dtypes.int32, - name="num_burnin_steps") - num_steps_between_results = ops.convert_to_tensor( - num_steps_between_results, - dtype=dtypes.int32, - name="num_steps_between_results") - - def _run_chain(num_steps, current_state, kernel_results): - """Runs the chain(s) for `num_steps`.""" - def _loop_body(iter_, current_state, kernel_results): - return [iter_ + 1] + list(kernel( - target_log_prob_fn, - current_state, - step_size, - num_leapfrog_steps, - seed, - kernel_results.current_target_log_prob, - kernel_results.current_grads_target_log_prob)) - while_loop_kwargs = dict( - cond=lambda iter_, *args: iter_ < num_steps, - body=_loop_body, - loop_vars=[ - np.int32(0), - current_state, - kernel_results, - ], - ) - if seed is not None: - while_loop_kwargs["parallel_iterations"] = 1 - return control_flow_ops.while_loop( - **while_loop_kwargs)[1:] # Lop-off "iter_". - - def _scan_body(args_list, iter_): - """Closure which implements `tf.scan` body.""" - current_state, kernel_results = args_list - return _run_chain( - 1 + array_ops.where(math_ops.equal(iter_, 0), - num_burnin_steps, - num_steps_between_results), - current_state, - kernel_results) - - scan_kwargs = dict( - fn=_scan_body, - elems=math_ops.range(num_results), # iter_: used to choose burnin. - initializer=[ - current_state, - _make_dummy_kernel_results( - current_state, - current_target_log_prob, - current_grads_target_log_prob), - ]) - if seed is not None: - scan_kwargs["parallel_iterations"] = 1 - return functional_ops.scan(**scan_kwargs) - - -def kernel(target_log_prob_fn, - current_state, - step_size, - num_leapfrog_steps, - seed=None, - current_target_log_prob=None, - current_grads_target_log_prob=None, - name=None): - """Runs one iteration of Hamiltonian Monte Carlo. - - Hamiltonian Monte Carlo (HMC) is a Markov chain Monte Carlo (MCMC) - algorithm that takes a series of gradient-informed steps to produce - a Metropolis proposal. This function applies one step of HMC to - randomly update the variable `x`. - - This function can update multiple chains in parallel. It assumes that all - leftmost dimensions of `current_state` index independent chain states (and are - therefore updated independently). The output of `target_log_prob_fn()` should - sum log-probabilities across all event dimensions. Slices along the rightmost - dimensions may have different target distributions; for example, - `current_state[0, :]` could have a different target distribution from - `current_state[1, :]`. This is up to `target_log_prob_fn()`. (The number of - independent chains is `tf.size(target_log_prob_fn(*current_state))`.) - - #### Examples: - - ##### Simple chain with warm-up. - - ```python - tfd = tf.contrib.distributions - - # Tuning acceptance rates: - dtype = np.float32 - target_accept_rate = 0.631 - num_warmup_iter = 500 - num_chain_iter = 500 - - x = tf.get_variable(name="x", initializer=dtype(1)) - step_size = tf.get_variable(name="step_size", initializer=dtype(1)) - - target = tfd.Normal(loc=dtype(0), scale=dtype(1)) - - next_x, other_results = hmc.kernel( - target_log_prob_fn=target.log_prob, - current_state=x, - step_size=step_size, - num_leapfrog_steps=3)[:4] - - x_update = x.assign(next_x) - - step_size_update = step_size.assign_add( - step_size * tf.where( - tf.exp(tf.minimum(other_results.log_accept_ratio), 0.) > - target_accept_rate, - 0.01, -0.01)) - - warmup = tf.group([x_update, step_size_update]) - - tf.global_variables_initializer().run() - - sess.graph.finalize() # No more graph building. - - # Warm up the sampler and adapt the step size - for _ in xrange(num_warmup_iter): - sess.run(warmup) - - # Collect samples without adapting step size - samples = np.zeros([num_chain_iter]) - for i in xrange(num_chain_iter): - _, x_, target_log_prob_, grad_ = sess.run([ - x_update, - x, - other_results.target_log_prob, - other_results.grads_target_log_prob]) - samples[i] = x_ - - print(samples.mean(), samples.std()) - ``` - - ##### Sample from more complicated posterior. - - I.e., - - ```none - W ~ MVN(loc=0, scale=sigma * eye(dims)) - for i=1...num_samples: - X[i] ~ MVN(loc=0, scale=eye(dims)) - eps[i] ~ Normal(loc=0, scale=1) - Y[i] = X[i].T * W + eps[i] - ``` - - ```python - tfd = tf.contrib.distributions - - def make_training_data(num_samples, dims, sigma): - dt = np.asarray(sigma).dtype - zeros = tf.zeros(dims, dtype=dt) - x = tfd.MultivariateNormalDiag( - loc=zeros).sample(num_samples, seed=1) - w = tfd.MultivariateNormalDiag( - loc=zeros, - scale_identity_multiplier=sigma).sample(seed=2) - noise = tfd.Normal( - loc=dt(0), - scale=dt(1)).sample(num_samples, seed=3) - y = tf.tensordot(x, w, axes=[[1], [0]]) + noise - return y, x, w - - def make_prior(sigma, dims): - # p(w | sigma) - return tfd.MultivariateNormalDiag( - loc=tf.zeros([dims], dtype=sigma.dtype), - scale_identity_multiplier=sigma) - - def make_likelihood(x, w): - # p(y | x, w) - return tfd.MultivariateNormalDiag( - loc=tf.tensordot(x, w, axes=[[1], [0]])) - - # Setup assumptions. - dtype = np.float32 - num_samples = 150 - dims = 10 - num_iters = int(5e3) - - true_sigma = dtype(0.5) - y, x, true_weights = make_training_data(num_samples, dims, true_sigma) - - # Estimate of `log(true_sigma)`. - log_sigma = tf.get_variable(name="log_sigma", initializer=dtype(0)) - sigma = tf.exp(log_sigma) - - # State of the Markov chain. - weights = tf.get_variable( - name="weights", - initializer=np.random.randn(dims).astype(dtype)) - - prior = make_prior(sigma, dims) - - def joint_log_prob_fn(w): - # f(w) = log p(w, y | x) - return prior.log_prob(w) + make_likelihood(x, w).log_prob(y) - - weights_update = weights.assign( - hmc.kernel(target_log_prob_fn=joint_log_prob, - current_state=weights, - step_size=0.1, - num_leapfrog_steps=5)[0]) - - with tf.control_dependencies([weights_update]): - loss = -prior.log_prob(weights) - - optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) - log_sigma_update = optimizer.minimize(loss, var_list=[log_sigma]) - - sess.graph.finalize() # No more graph building. - - tf.global_variables_initializer().run() - - sigma_history = np.zeros(num_iters, dtype) - weights_history = np.zeros([num_iters, dims], dtype) - - for i in xrange(num_iters): - _, sigma_, weights_, _ = sess.run([log_sigma_update, sigma, weights]) - weights_history[i, :] = weights_ - sigma_history[i] = sigma_ - - true_weights_ = sess.run(true_weights) - - # Should converge to something close to true_sigma. - plt.plot(sigma_history); - plt.ylabel("sigma"); - plt.xlabel("iteration"); - ``` - - Args: - target_log_prob_fn: Python callable which takes an argument like - `current_state` (or `*current_state` if it's a list) and returns its - (possibly unnormalized) log-density under the target distribution. - current_state: `Tensor` or Python `list` of `Tensor`s representing the - current state(s) of the Markov chain(s). The first `r` dimensions index - independent chains, `r = tf.rank(target_log_prob_fn(*current_state))`. - step_size: `Tensor` or Python `list` of `Tensor`s representing the step size - for the leapfrog integrator. Must broadcast with the shape of - `current_state`. Larger step sizes lead to faster progress, but too-large - step sizes make rejection exponentially more likely. When possible, it's - often helpful to match per-variable step sizes to the standard deviations - of the target distribution in each variable. - num_leapfrog_steps: Integer number of steps to run the leapfrog integrator - for. Total progress per HMC step is roughly proportional to `step_size * - num_leapfrog_steps`. - seed: Python integer to seed the random number generator. - current_target_log_prob: (Optional) `Tensor` representing the value of - `target_log_prob_fn` at the `current_state`. The only reason to - specify this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - current_grads_target_log_prob: (Optional) Python list of `Tensor`s - representing gradient of `current_target_log_prob` at the `current_state` - and wrt the `current_state`. Must have same shape as `current_state`. The - only reason to specify this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - name: Python `str` name prefixed to Ops created by this function. - Default value: `None` (i.e., "hmc_kernel"). - - Returns: - next_state: Tensor or Python list of `Tensor`s representing the state(s) - of the Markov chain(s) at each result step. Has same shape as - `current_state`. - kernel_results: `collections.namedtuple` of internal calculations used to - advance the chain. - - Raises: - ValueError: if there isn't one `step_size` or a list with same length as - `current_state`. - """ - with ops.name_scope( - name, "hmc_kernel", - [current_state, step_size, num_leapfrog_steps, seed, - current_target_log_prob, current_grads_target_log_prob]): - with ops.name_scope("initialize"): - [current_state_parts, step_sizes, current_target_log_prob, - current_grads_target_log_prob] = _prepare_args( - target_log_prob_fn, current_state, step_size, - current_target_log_prob, current_grads_target_log_prob, - maybe_expand=True) - independent_chain_ndims = distributions_util.prefer_static_rank( - current_target_log_prob) - current_momentums = [] - for s in current_state_parts: - current_momentums.append(random_ops.random_normal( - shape=array_ops.shape(s), - dtype=s.dtype.base_dtype, - seed=seed)) - seed = distributions_util.gen_new_seed( - seed, salt="hmc_kernel_momentums") - - num_leapfrog_steps = ops.convert_to_tensor( - num_leapfrog_steps, - dtype=dtypes.int32, - name="num_leapfrog_steps") - [ - proposed_momentums, - proposed_state_parts, - proposed_target_log_prob, - proposed_grads_target_log_prob, - ] = _leapfrog_integrator(current_momentums, - target_log_prob_fn, - current_state_parts, - step_sizes, - num_leapfrog_steps, - current_target_log_prob, - current_grads_target_log_prob) - - energy_change = _compute_energy_change(current_target_log_prob, - current_momentums, - proposed_target_log_prob, - proposed_momentums, - independent_chain_ndims) - log_accept_ratio = -energy_change - - # u < exp(log_accept_ratio), where u~Uniform[0,1) - # ==> log(u) < log_accept_ratio - random_value = random_ops.random_uniform( - shape=array_ops.shape(energy_change), - dtype=energy_change.dtype, - seed=seed) - random_negative = math_ops.log(random_value) - is_accepted = random_negative < log_accept_ratio - - accepted_target_log_prob = array_ops.where(is_accepted, - proposed_target_log_prob, - current_target_log_prob) - - next_state_parts = [_choose(is_accepted, - proposed_state_part, - current_state_part, - independent_chain_ndims) - for current_state_part, proposed_state_part - in zip(current_state_parts, proposed_state_parts)] - - accepted_grads_target_log_prob = [ - _choose(is_accepted, - proposed_grad, - grad, - independent_chain_ndims) - for proposed_grad, grad - in zip(proposed_grads_target_log_prob, current_grads_target_log_prob)] - - maybe_flatten = lambda x: x if _is_list_like(current_state) else x[0] - return [ - maybe_flatten(next_state_parts), - KernelResults( - log_accept_ratio=log_accept_ratio, - current_grads_target_log_prob=accepted_grads_target_log_prob, - current_target_log_prob=accepted_target_log_prob, - is_accepted=is_accepted, - proposed_grads_target_log_prob=proposed_grads_target_log_prob, - proposed_state=maybe_flatten(proposed_state_parts), - proposed_target_log_prob=proposed_target_log_prob, - ), - ] - - -def _leapfrog_integrator(current_momentums, - target_log_prob_fn, - current_state_parts, - step_sizes, - num_leapfrog_steps, - current_target_log_prob=None, - current_grads_target_log_prob=None, - name=None): - """Applies `num_leapfrog_steps` of the leapfrog integrator. - - Assumes a simple quadratic kinetic energy function: `0.5 ||momentum||**2`. - - #### Examples: - - ##### Simple quadratic potential. - - ```python - tfd = tf.contrib.distributions - - dims = 10 - num_iter = int(1e3) - dtype = np.float32 - - position = tf.placeholder(np.float32) - momentum = tf.placeholder(np.float32) - - [ - next_momentums, - next_positions, - ] = hmc._leapfrog_integrator( - current_momentums=[momentum], - target_log_prob_fn=tfd.MultivariateNormalDiag( - loc=tf.zeros(dims, dtype)).log_prob, - current_state_parts=[position], - step_sizes=0.1, - num_leapfrog_steps=3)[:2] - - sess.graph.finalize() # No more graph building. - - momentum_ = np.random.randn(dims).astype(dtype) - position_ = np.random.randn(dims).astype(dtype) - - positions = np.zeros([num_iter, dims], dtype) - for i in xrange(num_iter): - position_, momentum_ = sess.run( - [next_momentums[0], next_position[0]], - feed_dict={position: position_, momentum: momentum_}) - positions[i] = position_ - - plt.plot(positions[:, 0]); # Sinusoidal. - ``` - - Args: - current_momentums: Tensor containing the value(s) of the momentum - variable(s) to update. - target_log_prob_fn: Python callable which takes an argument like - `*current_state_parts` and returns its (possibly unnormalized) log-density - under the target distribution. - current_state_parts: Python `list` of `Tensor`s representing the current - state(s) of the Markov chain(s). The first `independent_chain_ndims` of - the `Tensor`(s) index different chains. - step_sizes: Python `list` of `Tensor`s representing the step size for the - leapfrog integrator. Must broadcast with the shape of - `current_state_parts`. Larger step sizes lead to faster progress, but - too-large step sizes make rejection exponentially more likely. When - possible, it's often helpful to match per-variable step sizes to the - standard deviations of the target distribution in each variable. - num_leapfrog_steps: Integer number of steps to run the leapfrog integrator - for. Total progress per HMC step is roughly proportional to `step_size * - num_leapfrog_steps`. - current_target_log_prob: (Optional) `Tensor` representing the value of - `target_log_prob_fn(*current_state_parts)`. The only reason to specify - this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - current_grads_target_log_prob: (Optional) Python list of `Tensor`s - representing gradient of `target_log_prob_fn(*current_state_parts`) wrt - `current_state_parts`. Must have same shape as `current_state_parts`. The - only reason to specify this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - name: Python `str` name prefixed to Ops created by this function. - Default value: `None` (i.e., "hmc_leapfrog_integrator"). - - Returns: - proposed_momentums: Updated value of the momentum. - proposed_state_parts: Tensor or Python list of `Tensor`s representing the - state(s) of the Markov chain(s) at each result step. Has same shape as - input `current_state_parts`. - proposed_target_log_prob: `Tensor` representing the value of - `target_log_prob_fn` at `next_state`. - proposed_grads_target_log_prob: Gradient of `proposed_target_log_prob` wrt - `next_state`. - - Raises: - ValueError: if `len(momentums) != len(state_parts)`. - ValueError: if `len(state_parts) != len(step_sizes)`. - ValueError: if `len(state_parts) != len(grads_target_log_prob)`. - TypeError: if `not target_log_prob.dtype.is_floating`. - """ - def _loop_body(step, - current_momentums, - current_state_parts, - ignore_current_target_log_prob, # pylint: disable=unused-argument - current_grads_target_log_prob): - return [step + 1] + list(_leapfrog_step(current_momentums, - target_log_prob_fn, - current_state_parts, - step_sizes, - current_grads_target_log_prob)) - - with ops.name_scope( - name, "hmc_leapfrog_integrator", - [current_momentums, current_state_parts, step_sizes, num_leapfrog_steps, - current_target_log_prob, current_grads_target_log_prob]): - if len(current_momentums) != len(current_state_parts): - raise ValueError("`momentums` must be in one-to-one correspondence " - "with `state_parts`") - num_leapfrog_steps = ops.convert_to_tensor(num_leapfrog_steps, - name="num_leapfrog_steps") - current_target_log_prob, current_grads_target_log_prob = ( - _maybe_call_fn_and_grads( - target_log_prob_fn, - current_state_parts, - current_target_log_prob, - current_grads_target_log_prob)) - return control_flow_ops.while_loop( - cond=lambda iter_, *args: iter_ < num_leapfrog_steps, - body=_loop_body, - loop_vars=[ - np.int32(0), # iter_ - current_momentums, - current_state_parts, - current_target_log_prob, - current_grads_target_log_prob, - ], - back_prop=False)[1:] # Lop-off "iter_". - - -def _leapfrog_step(current_momentums, - target_log_prob_fn, - current_state_parts, - step_sizes, - current_grads_target_log_prob, - name=None): - """Applies one step of the leapfrog integrator.""" - with ops.name_scope( - name, "_leapfrog_step", - [current_momentums, current_state_parts, step_sizes, - current_grads_target_log_prob]): - proposed_momentums = [m + 0.5 * ss * g for m, ss, g - in zip(current_momentums, - step_sizes, - current_grads_target_log_prob)] - proposed_state_parts = [x + ss * m for x, ss, m - in zip(current_state_parts, - step_sizes, - proposed_momentums)] - proposed_target_log_prob = target_log_prob_fn(*proposed_state_parts) - if not proposed_target_log_prob.dtype.is_floating: - raise TypeError("`target_log_prob_fn` must produce a `Tensor` " - "with `float` `dtype`.") - proposed_grads_target_log_prob = gradients_ops.gradients( - proposed_target_log_prob, proposed_state_parts) - if any(g is None for g in proposed_grads_target_log_prob): - raise ValueError( - "Encountered `None` gradient. Does your target `target_log_prob_fn` " - "access all `tf.Variable`s via `tf.get_variable`?\n" - " current_state_parts: {}\n" - " proposed_state_parts: {}\n" - " proposed_grads_target_log_prob: {}".format( - current_state_parts, - proposed_state_parts, - proposed_grads_target_log_prob)) - proposed_momentums = [m + 0.5 * ss * g for m, ss, g - in zip(proposed_momentums, - step_sizes, - proposed_grads_target_log_prob)] - return [ - proposed_momentums, - proposed_state_parts, - proposed_target_log_prob, - proposed_grads_target_log_prob, - ] - - -def _compute_energy_change(current_target_log_prob, - current_momentums, - proposed_target_log_prob, - proposed_momentums, - independent_chain_ndims, - name=None): - """Helper to `kernel` which computes the energy change.""" - with ops.name_scope( - name, "compute_energy_change", - ([current_target_log_prob, proposed_target_log_prob, - independent_chain_ndims] + - current_momentums + proposed_momentums)): - # Abbreviate lk0=log_kinetic_energy and lk1=proposed_log_kinetic_energy - # since they're a mouthful and lets us inline more. - lk0, lk1 = [], [] - for current_momentum, proposed_momentum in zip(current_momentums, - proposed_momentums): - axis = math_ops.range(independent_chain_ndims, - array_ops.rank(current_momentum)) - lk0.append(_log_sum_sq(current_momentum, axis)) - lk1.append(_log_sum_sq(proposed_momentum, axis)) - - lk0 = -np.log(2.) + math_ops.reduce_logsumexp(array_ops.stack(lk0, axis=-1), - axis=-1) - lk1 = -np.log(2.) + math_ops.reduce_logsumexp(array_ops.stack(lk1, axis=-1), - axis=-1) - lp0 = -current_target_log_prob # potential - lp1 = -proposed_target_log_prob # proposed_potential - x = array_ops.stack([lp1, math_ops.exp(lk1), -lp0, -math_ops.exp(lk0)], - axis=-1) - - # The sum is NaN if any element is NaN or we see both +Inf and -Inf. - # Thus we will replace such rows with infinite energy change which implies - # rejection. Recall that float-comparisons with NaN are always False. - is_sum_determinate = ( - math_ops.reduce_all(math_ops.is_finite(x) | (x >= 0.), axis=-1) & - math_ops.reduce_all(math_ops.is_finite(x) | (x <= 0.), axis=-1)) - is_sum_determinate = array_ops.tile( - is_sum_determinate[..., array_ops.newaxis], - multiples=array_ops.concat([ - array_ops.ones(array_ops.rank(is_sum_determinate), - dtype=dtypes.int32), - [4], - ], axis=0)) - x = array_ops.where(is_sum_determinate, - x, - array_ops.fill(array_ops.shape(x), - value=x.dtype.as_numpy_dtype(np.inf))) - - return math_ops.reduce_sum(x, axis=-1) - - -def _choose(is_accepted, - accepted, - rejected, - independent_chain_ndims, - name=None): - """Helper to `kernel` which expand_dims `is_accepted` to apply tf.where.""" - def _expand_is_accepted_like(x): - with ops.name_scope("_choose"): - expand_shape = array_ops.concat([ - array_ops.shape(is_accepted), - array_ops.ones([array_ops.rank(x) - array_ops.rank(is_accepted)], - dtype=dtypes.int32), - ], axis=0) - multiples = array_ops.concat([ - array_ops.ones([array_ops.rank(is_accepted)], dtype=dtypes.int32), - array_ops.shape(x)[independent_chain_ndims:], - ], axis=0) - m = array_ops.tile(array_ops.reshape(is_accepted, expand_shape), - multiples) - m.set_shape(x.shape) - return m - with ops.name_scope(name, "_choose", values=[ - is_accepted, accepted, rejected, independent_chain_ndims]): - return array_ops.where(_expand_is_accepted_like(accepted), - accepted, - rejected) - - -def _maybe_call_fn_and_grads(fn, - fn_arg_list, - fn_result=None, - grads_fn_result=None, - description="target_log_prob"): - """Helper which computes `fn_result` and `grads` if needed.""" - fn_arg_list = (list(fn_arg_list) if _is_list_like(fn_arg_list) - else [fn_arg_list]) - if fn_result is None: - fn_result = fn(*fn_arg_list) - if not fn_result.dtype.is_floating: - raise TypeError("`{}` must be a `Tensor` with `float` `dtype`.".format( - description)) - if grads_fn_result is None: - grads_fn_result = gradients_ops.gradients( - fn_result, fn_arg_list) - if len(fn_arg_list) != len(grads_fn_result): - raise ValueError("`{}` must be in one-to-one correspondence with " - "`grads_{}`".format(*[description]*2)) - if any(g is None for g in grads_fn_result): - raise ValueError("Encountered `None` gradient.") - return fn_result, grads_fn_result - - -def _prepare_args(target_log_prob_fn, state, step_size, - target_log_prob=None, grads_target_log_prob=None, - maybe_expand=False, description="target_log_prob"): - """Helper which processes input args to meet list-like assumptions.""" - state_parts = list(state) if _is_list_like(state) else [state] - state_parts = [ops.convert_to_tensor(s, name="state") - for s in state_parts] - target_log_prob, grads_target_log_prob = _maybe_call_fn_and_grads( - target_log_prob_fn, - state_parts, - target_log_prob, - grads_target_log_prob, - description) - step_sizes = list(step_size) if _is_list_like(step_size) else [step_size] - step_sizes = [ - ops.convert_to_tensor( - s, name="step_size", dtype=target_log_prob.dtype) - for s in step_sizes] - if len(step_sizes) == 1: - step_sizes *= len(state_parts) - if len(state_parts) != len(step_sizes): - raise ValueError("There should be exactly one `step_size` or it should " - "have same length as `current_state`.") - maybe_flatten = lambda x: x if maybe_expand or _is_list_like(state) else x[0] - return [ - maybe_flatten(state_parts), - maybe_flatten(step_sizes), - target_log_prob, - grads_target_log_prob, - ] - - -def _is_list_like(x): - """Helper which returns `True` if input is `list`-like.""" - return isinstance(x, (tuple, list)) - - -def _log_sum_sq(x, axis=None): - """Computes log(sum(x**2)).""" - return math_ops.reduce_logsumexp(2. * math_ops.log(math_ops.abs(x)), axis) diff --git a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings.py b/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings.py deleted file mode 100644 index e7fcbc65ef..0000000000 --- a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Functions to create a Markov Chain Monte Carlo Metropolis step.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -# go/tf-wildcard-import -# pylint: disable=wildcard-import -from tensorflow.contrib.bayesflow.python.ops.metropolis_hastings_impl import * -# pylint: enable=wildcard-import -from tensorflow.python.util.all_util import remove_undocumented - -_allowed_symbols = [ - 'kernel', - 'evolve', - 'proposal_uniform', - 'proposal_normal', -] - -remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py b/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py deleted file mode 100644 index 05aa134ed5..0000000000 --- a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py +++ /dev/null @@ -1,527 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Metropolis-Hastings and proposal distributions. - -@@kernel -@@evolve -@@proposal_uniform -@@proposal_normal -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections - -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops -from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import random_ops -from tensorflow.python.ops import state_ops - -__all__ = [ - "kernel", - "evolve", - "proposal_uniform", - "proposal_normal", -] - - -KernelResults = collections.namedtuple( - "KernelResults", - [ - "log_accept_ratio", - "current_target_log_prob", # "Current result" means "accepted". - "is_accepted", - "proposed_state", - ]) - - -def kernel(target_log_prob_fn, - proposal_fn, - current_state, - seed=None, - current_target_log_prob=None, - name=None): - """Runs the Metropolis-Hastings transition kernel. - - This function can update multiple chains in parallel. It assumes that all - leftmost dimensions of `current_state` index independent chain states (and are - therefore updated independently). The output of `target_log_prob_fn()` should - sum log-probabilities across all event dimensions. Slices along the rightmost - dimensions may have different target distributions; for example, - `current_state[0, :]` could have a different target distribution from - `current_state[1, :]`. This is up to `target_log_prob_fn()`. (The number of - independent chains is `tf.size(target_log_prob_fn(*current_state))`.) - - Args: - target_log_prob_fn: Python callable which takes an argument like - `current_state` (or `*current_state` if it's a list) and returns its - (possibly unnormalized) log-density under the target distribution. - proposal_fn: Python callable which takes an argument like `current_state` - (or `*current_state` if it's a list) and returns a tuple of proposed - states of same shape as `state`, and a log ratio `Tensor` of same shape - as `current_target_log_prob`. The log ratio is the log-probability of - `state` given proposed states minus the log-probability of proposed - states given `state`. If the proposal is symmetric, set the second value - to `None`: this enables more efficient computation than explicitly - supplying a tensor of zeros. - current_state: `Tensor` or Python `list` of `Tensor`s representing the - current state(s) of the Markov chain(s). The first `r` dimensions index - independent chains, `r = tf.rank(target_log_prob_fn(*current_state))`. - seed: Python integer to seed the random number generator. - current_target_log_prob: (Optional) `Tensor` representing the value of - `target_log_prob_fn` at the `current_state`. The only reason to - specify this argument is to reduce TF graph size. - Default value: `None` (i.e., compute as needed). - name: A name of the operation (optional). - - Returns: - next_state: Tensor or Python list of `Tensor`s representing the state(s) - of the Markov chain(s) at each result step. Has same shape as - `current_state`. - kernel_results: `collections.namedtuple` of internal calculations used to - advance the chain. - - #### Examples - - We illustrate Metropolis-Hastings on a Normal likelihood with - unknown mean. - - ```python - tfd = tf.contrib.distributions - tfp = tf.contrib.bayesflow - - loc = tf.get_variable("loc", initializer=1.) - x = tf.constant([0.0] * 50) - - def make_target_log_prob_fn(x): - def target_log_prob_fn(loc): - prior = tfd.Normal(loc=0., scale=1.) - likelihood = tfd.Independent( - tfd.Normal(loc=loc, scale=0.1), - reinterpreted_batch_ndims=1) - return prior.log_prob(loc) + likelihood.log_prob(x) - return target_log_prob_fn - - next_state, kernel_results = tfp.metropolis_hastings.kernel( - target_log_prob_fn=make_target_log_prob_fn(x), - proposal_fn=tfp.metropolis_hastings.proposal_normal(), - current_state=loc) - loc_update = loc.assign(next_state) - ``` - - We illustrate Metropolis-Hastings on a Normal likelihood with - unknown mean and variance. We apply 4 chains. - - ```python - tfd = tf.contrib.distributions - tfp = tf.contrib.bayesflow - - num_chains = 4 - loc = tf.get_variable("loc", shape=[num_chains], - initializer=tf.random_normal_initializer()) - scale = tf.get_variable("scale", shape=[num_chains], - initializer=tf.ones_initializer()) - x = tf.constant([0.0] * 50) - - def make_target_log_prob_fn(x): - data = tf.reshape(x, shape=[-1, 1]) - def target_log_prob_fn(loc, scale): - prior_loc = tfd.Normal(loc=0., scale=1.) - prior_scale = tfd.InverseGamma(concentration=1., rate=1.) - likelihood = tfd.Independent( - tfd.Normal(loc=loc, scale=scale), - reinterpreted_batch_ndims=1) - return (prior_loc.log_prob(loc) + - prior_scale.log_prob(scale) + - likelihood.log_prob(data)) - return target_log_prob_fn - - def proposal_fn(loc, scale): - loc_proposal = tfp.metropolis_hastings.proposal_normal() - scale_proposal = tfp.metropolis_hastings.proposal_uniform(minval=-1.) - proposed_loc, _ = loc_proposal(loc) - proposed_scale, _ = scale_proposal(scale) - proposed_scale = tf.maximum(proposed_scale, 0.01) - return [proposed_loc, proposed_scale], None - - next_state, kernel_results = tfp.metropolis_hastings.kernel( - target_log_prob_fn=make_target_log_prob_fn(x), - proposal_fn=proposal_fn, - current_state=[loc, scale]) - train_op = tf.group(loc.assign(next_state[0]), - scale.assign(next_state[1])) - ``` - - """ - with ops.name_scope( - name, "metropolis_hastings_kernel", - [current_state, seed, current_target_log_prob]): - with ops.name_scope("initialize"): - maybe_expand = lambda x: list(x) if _is_list_like(x) else [x] - current_state_parts = maybe_expand(current_state) - if current_target_log_prob is None: - current_target_log_prob = target_log_prob_fn(*current_state_parts) - - proposed_state, log_transit_ratio = proposal_fn(*current_state_parts) - proposed_state_parts = maybe_expand(proposed_state) - - proposed_target_log_prob = target_log_prob_fn(*proposed_state_parts) - - with ops.name_scope( - "accept_reject", - [current_state_parts, proposed_state_parts, - current_target_log_prob, proposed_target_log_prob]): - log_accept_ratio = proposed_target_log_prob - current_target_log_prob - if log_transit_ratio is not None: - # If the log_transit_ratio is None, then assume the proposal is - # symmetric, i.e., - # log p(old | new) - log p(new | old) = 0. - log_accept_ratio += log_transit_ratio - - # u < exp(log_accept_ratio), where u~Uniform[0,1) - # ==> log(u) < log_accept_ratio - random_value = random_ops.random_uniform( - array_ops.shape(log_accept_ratio), - dtype=log_accept_ratio.dtype, - seed=seed) - random_negative = math_ops.log(random_value) - is_accepted = random_negative < log_accept_ratio - next_state_parts = [array_ops.where(is_accepted, - proposed_state_part, - current_state_part) - for proposed_state_part, current_state_part in - zip(proposed_state_parts, current_state_parts)] - accepted_log_prob = array_ops.where(is_accepted, - proposed_target_log_prob, - current_target_log_prob) - maybe_flatten = lambda x: x if _is_list_like(current_state) else x[0] - return [ - maybe_flatten(next_state_parts), - KernelResults( - log_accept_ratio=log_accept_ratio, - current_target_log_prob=accepted_log_prob, - is_accepted=is_accepted, - proposed_state=maybe_flatten(proposed_state_parts), - ), - ] - - -def evolve(initial_sample, - initial_log_density, - initial_log_accept_ratio, - target_log_prob_fn, - proposal_fn, - n_steps=1, - seed=None, - name=None): - """Performs `n_steps` of the Metropolis-Hastings update. - - Given a probability density function, `f(x)` and a proposal scheme which - generates new points from old, this `Op` returns a tensor - which may be used to generate approximate samples from the target distribution - using the Metropolis-Hastings algorithm. These samples are from a Markov chain - whose equilibrium distribution matches the target distribution. - - The probability distribution may have an unknown normalization constan. - We parameterize the probability density as follows: - - ```none - f(x) = exp(L(x) + constant) - ``` - - Here `L(x)` is any continuous function with an (possibly unknown but finite) - upper bound, i.e. there exists a number beta such that - `L(x)< beta < infinity` for all x. The constant is the normalization needed - to make `f(x)` a probability density (as opposed to just a finite measure). - - Although `initial_sample` can be arbitrary, a poor choice may result in a - slow-to-mix chain. In many cases the best choice is the one that maximizes - the target density, i.e., choose `initial_sample` such that - `f(initial_sample) >= f(x)` for all `x`. - - - If the support of the distribution is a strict subset of R^n (but of non zero - measure), then the unnormalized log-density `L(x)` should return `-infinity` - outside the support domain. This effectively forces the sampler to only - explore points in the regions of finite support. - - Usage: - This function is meant to be wrapped up with some of the common proposal - schemes (e.g. random walk, Langevin diffusion etc) to produce a more user - friendly interface. However, it may also be used to create bespoke samplers. - - The following example, demonstrates the use to generate a 1000 uniform random - walk Metropolis samplers run in parallel for the normal target distribution. - - ```python - n = 3 # dimension of the problem - - # Generate 1000 initial values randomly. Each of these would be an - # independent starting point for a Markov chain. - state = tf.get_variable( - "state", - initializer=tf.random_normal([1000, n], - mean=3.0, - dtype=tf.float64, - seed=42)) - - # Computes the log(p(x)) for the unit normal density and ignores the - # normalization constant. - def log_density(x): - return -tf.reduce_sum(x * x, reduction_indices=-1) / 2.0 - - # Initial log-density value - state_log_density = tf.get_variable( - "state_log_density", - initializer=log_density(state.initialized_value())) - - # A variable to store the log_acceptance_ratio: - log_acceptance_ratio = tf.get_variable( - "log_acceptance_ratio", - initializer=tf.zeros([1000], dtype=tf.float64)) - - # Generates random proposals by moving each coordinate uniformly and - # independently in a box of size 2 centered around the current value. - # Returns the new point and also the log of the Hastings ratio (the - # ratio of the probability of going from the proposal to origin and the - # probability of the reverse transition). When this ratio is 1, the value - # may be omitted and replaced by None. - def random_proposal(x): - return (x + tf.random_uniform(tf.shape(x), minval=-1, maxval=1, - dtype=x.dtype, seed=12)), None - - # Create the op to propagate the chain for 100 steps. - stepper = mh.evolve( - state, state_log_density, log_acceptance_ratio, - log_density, random_proposal, n_steps=100, seed=123) - init = tf.initialize_all_variables() - with tf.Session() as sess: - sess.run(init) - # Run the chains for a total of 1000 steps and print out the mean across - # the chains every 100 iterations. - for n_iter in range(10): - # Executing the stepper advances the chain to the next state. - sess.run(stepper) - # Print out the current value of the mean(sample) for every dimension. - print(np.mean(sess.run(state), 0)) - # Estimated covariance matrix - samples = sess.run(state) - print(np.cov(samples, rowvar=False)) - ``` - - Args: - initial_sample: A float-like `tf.Variable` of any shape that can - be consumed by the `target_log_prob_fn` and `proposal_fn` - callables. - initial_log_density: Float-like `tf.Variable` with `dtype` and shape - equivalent to `target_log_prob_fn(initial_sample)`, i.e., matching - the result of `target_log_prob_fn` invoked at `current_state`. - initial_log_accept_ratio: A `tf.Variable` with `dtype` and shape matching - `initial_log_density`. Stands for the log of Metropolis-Hastings - acceptance ratio after propagating the chain for `n_steps`. - target_log_prob_fn: A Python callable evaluated at - `current_state` and returning a float-like `Tensor` of log target-density - up to a normalizing constant. In other words, - `target_log_prob_fn(x) = log(g(x))`, where - `target_density = g(x)/Z` for some constant `A`. The shape of the input - tensor is the same as the shape of the `current_state`. The shape of the - output tensor is either - (a). Same as the input shape if the density being sampled is one - dimensional, or - (b). If the density is defined for `events` of shape - `event_shape = [E1, E2, ... Ee]`, then the input tensor should be of - shape `batch_shape + event_shape`, here `batch_shape = [B1, ..., Bb]` - and the result must be of shape [B1, ..., Bb]. For example, if the - distribution that is being sampled is a 10 dimensional normal, - then the input tensor may be of shape [100, 10] or [30, 20, 10]. The - last dimension will then be 'consumed' by `target_log_prob_fn` - and it should return tensors of shape [100] and [30, 20] respectively. - proposal_fn: A callable accepting a real valued `Tensor` of current sample - points and returning a tuple of two `Tensors`. The first element of the - pair should be a `Tensor` containing the proposal state and should have - the same shape as the input `Tensor`. The second element of the pair gives - the log of the ratio of the probability of transitioning from the - proposal points to the input points and the probability of transitioning - from the input points to the proposal points. If the proposal is - symmetric, i.e. - Probability(Proposal -> Current) = Probability(Current -> Proposal) - the second value should be set to None instead of explicitly supplying a - tensor of zeros. In addition to being convenient, this also leads to a - more efficient graph. - n_steps: A positive `int` or a scalar `int32` tensor. Sets the number of - iterations of the chain. - seed: `int` or None. The random seed for this `Op`. If `None`, no seed is - applied. - name: A string that sets the name for this `Op`. - - Returns: - forward_step: an `Op` to step the Markov chain forward for `n_steps`. - """ - - with ops.name_scope(name, "metropolis_hastings", [initial_sample]): - current_state = initial_sample - current_target_log_prob = initial_log_density - log_accept_ratio = initial_log_accept_ratio - - def step(i, current_state, current_target_log_prob, log_accept_ratio): - """Wrap single Markov chain iteration in `while_loop`.""" - next_state, kernel_results = kernel( - target_log_prob_fn=target_log_prob_fn, - proposal_fn=proposal_fn, - current_state=current_state, - current_target_log_prob=current_target_log_prob, - seed=seed) - accepted_log_prob = kernel_results.current_target_log_prob - log_accept_ratio = kernel_results.log_accept_ratio - return i + 1, next_state, accepted_log_prob, log_accept_ratio - - (_, accepted_state, accepted_target_log_prob, accepted_log_accept_ratio) = ( - control_flow_ops.while_loop( - cond=lambda i, *ignored_args: i < n_steps, - body=step, - loop_vars=[ - 0, # i - current_state, - current_target_log_prob, - log_accept_ratio, - ], - parallel_iterations=1 if seed is not None else 10, - # TODO(b/73775595): Confirm optimal setting of swap_memory. - swap_memory=1)) - - forward_step = control_flow_ops.group( - state_ops.assign(current_target_log_prob, accepted_target_log_prob), - state_ops.assign(current_state, accepted_state), - state_ops.assign(log_accept_ratio, accepted_log_accept_ratio)) - - return forward_step - - -def proposal_uniform(step_size=1., - seed=None, - name=None): - """Returns a callable that adds a random uniform tensor to the input. - - This function returns a callable that accepts one `Tensor` argument of any - shape and a real data type (i.e. `tf.float32` or `tf.float64`). It adds a - sample from a random uniform distribution drawn from [-stepsize, stepsize] - to its input. It also returns the log of the ratio of the probability of - moving from the input point to the proposed point, but since this log ratio is - identically equal to 0 (because the probability of drawing a value `x` from - the symmetric uniform distribution is the same as the probability of drawing - `-x`), it simply returns None for the second element of the returned tuple. - - Args: - step_size: A positive `float` or a scalar tensor of real dtype - controlling the scale of the uniform distribution. - If step_size = a, then draws are made uniformly from [-a, a]. - seed: `int` or None. The random seed for this `Op`. If `None`, no seed is - applied. - name: A string that sets the name for this `Op`. - - Returns: - proposal_fn: A callable accepting one float-like `Tensor` and returning a - 2-tuple. The first value in the tuple is a `Tensor` of the same shape and - dtype as the input argument and the second element of the tuple is None. - """ - - with ops.name_scope(name, "proposal_uniform", [step_size]): - step_size = ops.convert_to_tensor(step_size, name="step_size") - - def proposal_fn(input_state, name=None): - """Adds a uniform perturbation to the input state. - - Args: - input_state: A `Tensor` of any shape and real dtype. - name: A string that sets the name for this `Op`. - - Returns: - proposal_state: A float-like `Tensor` with `dtype` and shape matching - `input_state`. - log_transit_ratio: `None`. Proposal is symmetric. - """ - with ops.name_scope(name, "proposer", [input_state]): - input_state = ops.convert_to_tensor(input_state, name="input_state") - return input_state + random_ops.random_uniform( - array_ops.shape(input_state), - minval=-step_size, - maxval=step_size, - seed=seed), None - return proposal_fn - - -def proposal_normal(scale=1., - seed=None, - name=None): - """Returns a callable that adds a random normal tensor to the input. - - This function returns a callable that accepts one `Tensor` argument of any - shape and a real data type (i.e. `tf.float32` or `tf.float64`). The callable - adds a sample from a normal distribution with the supplied standard deviation - and zero mean to its input argument (called the proposal point). - The callable returns a tuple with the proposal point as the first element. - The second element is identically `None`. It is included so the callable is - compatible with the expected signature of the proposal scheme argument in the - `metropolis_hastings` function. A value of `None` indicates that the - probability of going from the input point to the proposal point is equal to - the probability of going from the proposal point to the input point. - - Args: - scale: A positive `float` or a scalar tensor of any real dtype controlling - the scale of the normal distribution. - seed: `int` or None. The random seed for this `Op`. If `None`, no seed is - applied. - name: A string that sets the name for this `Op`. - - Returns: - proposal_fn: A callable accepting one float-like `Tensor` and returning a - 2-tuple. The first value in the tuple is a `Tensor` of the same shape and - dtype as the input argument and the second element of the tuple is None. - """ - - with ops.name_scope(name, "proposal_normal", [scale]): - scale = ops.convert_to_tensor(scale, name="scale") - - def proposal_fn(input_state, name=None): - """Adds a normal perturbation to the input state. - - Args: - input_state: A `Tensor` of any shape and real dtype. - name: A string that sets the name for this `Op`. - - Returns: - proposal_state: A float-like `Tensor` with `dtype` and shape matching - `input_state`. - log_transit_ratio: `None`. Proposal is symmetric. - """ - - with ops.name_scope(name, "proposer", [input_state]): - input_state = ops.convert_to_tensor(input_state, name="input_state") - return input_state + random_ops.random_normal( - array_ops.shape(input_state), - mean=0., - stddev=scale, - dtype=scale.dtype, - seed=seed), None - return proposal_fn - - -def _is_list_like(x): - """Helper which returns `True` if input is `list`-like.""" - return isinstance(x, (tuple, list)) -- GitLab From 56d1cfde15c04ebe27fe31409a724a56e7051b15 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 19:08:15 -0700 Subject: [PATCH 245/906] [XLA] Redesign: implement and test ternary ops. PiperOrigin-RevId: 190561679 --- .../xla/client/xla_client/xla_builder.cc | 42 +++- .../xla/client/xla_client/xla_builder.h | 4 + .../compiler/xla/service/shape_inference.cc | 8 +- .../compiler/xla/service/shape_inference.h | 3 + .../xla/tests/array_elementwise_ops_test.cc | 205 +++++++++--------- .../xla/tests/client_library_test_base.cc | 21 +- .../xla/tests/client_library_test_base.h | 2 + 7 files changed, 175 insertions(+), 110 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index 1b90b45bfb..fcaf393b6b 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -288,6 +288,44 @@ XlaOp XlaBuilder::BinaryOp( }()); } +XlaOp XlaBuilder::TernaryOp(HloOpcode triop, const XlaOp& lhs, const XlaOp& rhs, + const XlaOp& ehs) { + return NoteErrorOrReturn([&]() -> StatusOr { + HloInstructionProto instr; + TF_ASSIGN_OR_RETURN(const Shape& lhs_shape, lhs.GetShape()); + TF_ASSIGN_OR_RETURN(const Shape& rhs_shape, rhs.GetShape()); + TF_ASSIGN_OR_RETURN(const Shape& ehs_shape, ehs.GetShape()); + TF_ASSIGN_OR_RETURN(*instr.mutable_shape(), + ShapeInference::InferTernaryOpShape( + triop, lhs_shape, rhs_shape, ehs_shape)); + XlaOp updated_lhs = lhs; + XlaOp updated_rhs = rhs; + XlaOp updated_ehs = ehs; + if (!ShapeUtil::IsTuple(instr.shape())) { + if (!ShapeUtil::IsTuple(lhs_shape) && + !ShapeUtil::SameDimensions(instr.shape(), lhs_shape)) { + // lhs is being implicitly broadcasted. Change to explicit. + TF_ASSIGN_OR_RETURN(updated_lhs, + AddBroadcastSequence(instr.shape(), lhs)); + } + if (!ShapeUtil::IsTuple(rhs_shape) && + !ShapeUtil::SameDimensions(instr.shape(), rhs_shape)) { + // rhs is being implicitly broadcasted. Change to explicit. + TF_ASSIGN_OR_RETURN(updated_rhs, + AddBroadcastSequence(instr.shape(), rhs)); + } + if (!ShapeUtil::IsTuple(ehs_shape) && + !ShapeUtil::SameDimensions(instr.shape(), ehs_shape)) { + // ehs is being implicitly broadcasted. Change to explicit. + TF_ASSIGN_OR_RETURN(updated_ehs, + AddBroadcastSequence(instr.shape(), ehs)); + } + } + return AddInstruction(std::move(instr), triop, + {updated_lhs, updated_rhs, updated_ehs}); + }()); +} + XlaOp XlaBuilder::Add(const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions) { return BinaryOp(HloOpcode::kAdd, lhs, rhs, broadcast_dimensions); @@ -449,7 +487,7 @@ void XlaBuilder::Trace(const string& tag, const XlaOp& operand) { XlaOp XlaBuilder::Select(const XlaOp& pred, const XlaOp& on_true, const XlaOp& on_false) { - return UnimplementedOp(); + return TernaryOp(HloOpcode::kSelect, pred, on_true, on_false); } XlaOp XlaBuilder::Tuple(tensorflow::gtl::ArraySlice elements) { @@ -755,7 +793,7 @@ XlaOp XlaBuilder::Neg(const XlaOp& operand) { XlaOp XlaBuilder::Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max) { - return UnimplementedOp(); + return TernaryOp(HloOpcode::kClamp, min, operand, max); } XlaOp XlaBuilder::Map(tensorflow::gtl::ArraySlice operands, diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index cc33356cc1..c5c35159e0 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -762,6 +762,10 @@ class XlaBuilder { XlaOp BinaryOp(HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs, tensorflow::gtl::ArraySlice broadcast_dimensions); + // Internal helper method that does the building for an arbitrary ternary op. + XlaOp TernaryOp(HloOpcode triop, const XlaOp& lhs, const XlaOp& rhs, + const XlaOp& ehs); + StatusOr InDimBroadcast( const Shape& shape, const XlaOp& operand, tensorflow::gtl::ArraySlice broadcast_dimensions); diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index 2a70ea0354..36456d552d 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -1038,8 +1038,12 @@ ShapeInference::InferDegenerateDimensionBroadcastShape( /* static */ StatusOr ShapeInference::InferTernaryOpShape( HloOpcode opcode, const HloInstruction* lhs, const HloInstruction* rhs, const HloInstruction* ehs) { - return InferTernaryOpShape(OpcodeToTernaryOperation(opcode), lhs->shape(), - rhs->shape(), ehs->shape()); + return InferTernaryOpShape(opcode, lhs->shape(), rhs->shape(), ehs->shape()); +} + +/* static */ StatusOr ShapeInference::InferTernaryOpShape( + HloOpcode opcode, const Shape& lhs, const Shape& rhs, const Shape& ehs) { + return InferTernaryOpShape(OpcodeToTernaryOperation(opcode), lhs, rhs, ehs); } /* static */ StatusOr ShapeInference::InferTernaryOpShape( diff --git a/tensorflow/compiler/xla/service/shape_inference.h b/tensorflow/compiler/xla/service/shape_inference.h index b6552a34ae..88830e6d25 100644 --- a/tensorflow/compiler/xla/service/shape_inference.h +++ b/tensorflow/compiler/xla/service/shape_inference.h @@ -70,6 +70,9 @@ class ShapeInference { static StatusOr InferTernaryOpShape(TernaryOperation operation, const Shape& lhs, const Shape& rhs, const Shape& ehs); + static StatusOr InferTernaryOpShape(HloOpcode opcode, const Shape& lhs, + const Shape& rhs, + const Shape& ehs); static StatusOr InferTernaryOpShape(HloOpcode opcode, const HloInstruction* lhs, const HloInstruction* rhs, diff --git a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc index fa7ac3ca9b..03c91745b9 100644 --- a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc +++ b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc @@ -244,7 +244,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoConstantU64s) { std::unique_ptr rhs_data = client_->TransferToServer(*rhs_literal).ConsumeValueOrDie(); - auto add = b.Add(lhs_param, rhs_param); + b.Add(lhs_param, rhs_param); std::vector expected(lhs.size()); for (int64 i = 0; i < lhs.size(); ++i) { @@ -1914,101 +1914,98 @@ XLA_TEST_F(ArrayElementwiseOpTest, RemTwoConstantS32s) { } XLA_TEST_F(ArrayElementwiseOpTest, NonNanClampF32) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto minimum = builder.ConstantR1({1.0f, -6.5f, 1.0f, 2.25f, 0.0f}); auto argument = builder.ConstantR1({2.0f, 10.0f, -5.0f, 1.0f, 10.0f}); auto maximum = builder.ConstantR1({3.0f, 0.5f, 25.5f, 5.0f, 123.0}); - auto clamp = builder.Clamp(minimum, argument, maximum); + builder.Clamp(minimum, argument, maximum); ComputeAndCompareR1(&builder, {2.0f, 0.5f, 1.0f, 2.25f, 10.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, ClampF32Scalar) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto minimum = builder.ConstantR0(0.0f); auto argument = builder.ConstantR1({2.0f, 10.0f, -5.0f, 1.0f, 4.0f}); auto maximum = builder.ConstantR0(5.0f); - auto clamp = builder.Clamp(minimum, argument, maximum); + builder.Clamp(minimum, argument, maximum); ComputeAndCompareR1(&builder, {2.0f, 5.0f, 0.0f, 1.0f, 4.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, ClampF32ScalarVector) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto min_scalar = builder.ConstantR0(0.0f); auto min_vector = builder.ConstantR1({1.0f, -6.5f, 1.0f, 2.25f, 0.0f}); auto arg_vector = builder.ConstantR1({2.0f, 10.0f, -5.0f, 1.0f, 4.0f}); auto max_scalar = builder.ConstantR0(3.0f); auto max_vector = builder.ConstantR1({3.0f, 0.5f, 25.5f, 5.0f, 123.0}); // Perform clamp with broadcasted scalar and vector. - auto clamp = builder.Add( - builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), - builder.Clamp(min_scalar, arg_vector, max_vector)), - builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), - builder.Clamp(min_scalar, arg_vector, max_scalar))); + builder.Add(builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), + builder.Clamp(min_scalar, arg_vector, max_vector)), + builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), + builder.Clamp(min_scalar, arg_vector, max_scalar))); ComputeAndCompareR1(&builder, {8.0f, 7.0f, 2.0f, 6.5f, 14.0f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, ClampS32Vector) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto min_vector = builder.ConstantR1({1, -6, 1, 2, 0, -5}); auto arg_vector = builder.ConstantR1({2, 10, -5, 1, 4, 10}); auto max_vector = builder.ConstantR1({3, 0, 25, 5, 123, -1}); - auto clamp = builder.Clamp(min_vector, arg_vector, max_vector); + builder.Clamp(min_vector, arg_vector, max_vector); ComputeAndCompareR1(&builder, {2, 0, 1, 2, 4, -1}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ClampS32ScalarVector) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto min_scalar = builder.ConstantR0(0); auto min_vector = builder.ConstantR1({1, -6, 1, 2, 0}); auto arg_vector = builder.ConstantR1({2, 10, -5, 1, 4}); auto max_scalar = builder.ConstantR0(3); auto max_vector = builder.ConstantR1({3, 1, 25, 5, 123}); // Perform clamp with broadcasted scalar and vector. - auto clamp = builder.Add( - builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), - builder.Clamp(min_scalar, arg_vector, max_vector)), - builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), - builder.Clamp(min_scalar, arg_vector, max_scalar))); + builder.Add(builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), + builder.Clamp(min_scalar, arg_vector, max_vector)), + builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), + builder.Clamp(min_scalar, arg_vector, max_scalar))); ComputeAndCompareR1(&builder, {8, 8, 2, 6, 14}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ClampU32Vector) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto min_vector = builder.ConstantR1({1, 2, 1, 2, 0, ~0u - 4}); auto arg_vector = builder.ConstantR1({2, 10, 5, 1, 4, 10}); auto max_vector = builder.ConstantR1({3, 5, 25, 5, 123, ~0u}); - auto clamp = builder.Clamp(min_vector, arg_vector, max_vector); + builder.Clamp(min_vector, arg_vector, max_vector); ComputeAndCompareR1(&builder, {2, 5, 5, 2, 4, ~0u - 4}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, ClampU32ScalarVector) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto min_scalar = builder.ConstantR0(0); auto min_vector = builder.ConstantR1({1, 0, 1, 2, 0}); auto arg_vector = builder.ConstantR1({2, 10, 0, 1, 4}); auto max_scalar = builder.ConstantR0(3); auto max_vector = builder.ConstantR1({3, 1, 25, 5, 123}); // Perform clamp with broadcasted scalar and vector. - auto clamp = builder.Add( - builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), - builder.Clamp(min_scalar, arg_vector, max_vector)), - builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), - builder.Clamp(min_scalar, arg_vector, max_scalar))); + builder.Add(builder.Add(builder.Clamp(min_vector, arg_vector, max_scalar), + builder.Clamp(min_scalar, arg_vector, max_vector)), + builder.Add(builder.Clamp(min_vector, arg_vector, max_vector), + builder.Clamp(min_scalar, arg_vector, max_scalar))); ComputeAndCompareR1(&builder, {8, 8, 2, 6, 14}, {}); } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr param0_literal = Literal::CreateR1({1.1f, 2.2f, 3.3f, 5.5f}); @@ -2022,7 +2019,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersF32s) { auto p0 = builder.Parameter(0, param0_literal->shape(), "param0"); auto p1 = builder.Parameter(1, param1_literal->shape(), "param1"); - auto add = builder.Add(p0, p1); + builder.Add(p0, p1); ComputeAndCompareR1(&builder, {8.3f, 4.5f, 6.7f, 11.1f}, {param0_data.get(), param1_data.get()}, @@ -2030,7 +2027,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersZeroElementF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr param0_literal = Literal::CreateR3FromArray3D(Array3D(0, 7, 0)); @@ -2044,7 +2041,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersZeroElementF32s) { auto p0 = builder.Parameter(0, param0_literal->shape(), "param0"); auto p1 = builder.Parameter(1, param1_literal->shape(), "param1"); - auto add = builder.Add(p0, p1); + builder.Add(p0, p1); Array3D expected(0, 7, 0); ComputeAndCompareR3( @@ -2052,7 +2049,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddTwoParametersZeroElementF32s) { } XLA_TEST_F(ArrayElementwiseOpTest, AddParameterToConstantF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr param0_literal = Literal::CreateR1({1.1f, 2.2f, 3.3f, 5.5f}); @@ -2061,35 +2058,35 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddParameterToConstantF32s) { auto a = builder.ConstantR1({1.1f, 2.2f, 3.3f, 4.4f}); auto p = builder.Parameter(0, param0_literal->shape(), "param0"); - auto add = builder.Add(a, p); + builder.Add(a, p); ComputeAndCompareR1(&builder, {2.2f, 4.4f, 6.6f, 9.9f}, {param0_data.get()}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, CosF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({3.14159f, 0.0f, 1.570796f, -0.78539f}); - auto result = builder.Cos(a); + builder.Cos(a); ComputeAndCompareR1(&builder, {-1.0f, 1.0f, 0.0f, 0.707107f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, SinF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({3.14159f, 0.0f, 1.570796f, -0.78539f}); - auto result = builder.Sin(a); + builder.Sin(a); ComputeAndCompareR1(&builder, {0.0f, 0.0f, 1.0f, -0.707107f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, Atan2F32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({0.0f, 5.0f, 0.0f, -3.0f, 2.0f, -8.0f}); auto b = builder.ConstantR1({6.0f, 0.0f, -4.0f, 0.0f, 2.0f, 8.0f}); - auto atan = builder.Atan2(a, b); + builder.Atan2(a, b); ComputeAndCompareR1( &builder, @@ -2098,9 +2095,9 @@ XLA_TEST_F(ArrayElementwiseOpTest, Atan2F32s) { } XLA_TEST_F(ArrayElementwiseOpTest, TanhF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({-2.5f, 3.14f, 2.25f}); - auto result = builder.Tanh(a); + builder.Tanh(a); ComputeAndCompareR1(&builder, {-0.986614f, 0.996260f, 0.978026}, {}, error_spec_); @@ -2110,7 +2107,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, TanhF32sVector) { // This is like the test ArrayElementwiseOpTest.TanhF32s above, except that // the input tensor is large enough to exercise the vectorized tanh // implementation on XLA CPU. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto input_literal = Literal::CreateR1( {1.02, -0.32, 0.85, 0.90, 1.23, -0.91, -0.49, 0.80, -0.67, 0.16, -0.07, 0.39, -0.41, 0.04, 1.36, 1.25, 0.41, 0.65, -1.08, 0.32, @@ -2149,7 +2146,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, TanhF32sVector) { XLA_TEST_F(ArrayElementwiseOpTest, ExpF32sVector) { // The input tensor is large enough to exercise the vectorized exp // implementation on XLA CPU. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // Just to help make sense of the scales here -- exp(89) saturates float32 and // exp(-10) is smaller than our error spec. @@ -2185,7 +2182,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, ExpF32sVector) { XLA_TEST_F(ArrayElementwiseOpTest, LogF32sVector) { // The input tensor is large enough to exercise the vectorized exp // implementation on XLA CPU. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr input_literal = Literal::CreateR1( {-1.29, -1.41, -1.25, -13.5, -11.7, -17.9, -198, @@ -2225,14 +2222,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddChainFoldLeft) { // / / // b -----/ / // c---------------------/ - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({1.1f, 2.2f, 3.3f, 4.4f}); auto b = builder.ConstantR1({2.1f, 3.2f, 4.3f, 5.4f}); auto c = builder.ConstantR1({-3.3f, -15.5f, -7.7f, -29.9f}); auto add = builder.Add(a, b); - auto add2 = builder.Add(add, c); + builder.Add(add, c); ComputeAndCompareR1(&builder, {-0.1f, -10.1f, -0.1f, -20.1f}, {}, error_spec_); @@ -2243,14 +2240,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddChainFoldRight) { // / / // c -----/ / // a---------------------/ - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({91.1f, 2.2f, 3.3f, 4.4f}); auto b = builder.ConstantR1({2.1f, 3.2f, 4.3f, 5.4f}); auto c = builder.ConstantR1({-3.3f, -15.5f, -7.7f, -29.9f}); auto add = builder.Add(b, c); - auto add2 = builder.Add(a, add); + builder.Add(a, add); ComputeAndCompareR1(&builder, {89.9f, -10.1f, -0.1f, -20.1f}, {}, error_spec_); @@ -2260,14 +2257,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddWithNeg) { // a ----- (neg) ----- (add) // / // b ----- (neg) ----/ - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({91.1f, 2.2f, 3.3f, 4.4f}); auto b = builder.ConstantR1({2.1f, 3.2f, 4.3f, 5.4f}); auto neg_a = builder.Neg(a); auto neg_b = builder.Neg(b); - auto result = builder.Add(neg_a, neg_b); + builder.Add(neg_a, neg_b); ComputeAndCompareR1(&builder, {-93.2f, -5.4f, -7.6f, -9.8f}, {}, error_spec_); @@ -2281,7 +2278,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddChainTwoSide) { // c ------ (add) ------------/ // / // d -----/ - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR1({91.1f, 2.2f, 3.3f, 4.4f}); auto b = builder.ConstantR1({2.1f, 3.2f, 4.3f, 5.4f}); @@ -2290,19 +2287,19 @@ XLA_TEST_F(ArrayElementwiseOpTest, AddChainTwoSide) { auto add_ab = builder.Add(a, b); auto add_cd = builder.Add(c, d); - auto add_all = builder.Add(add_ab, add_cd); + builder.Add(add_ab, add_cd); ComputeAndCompareR1(&builder, {70.9f, -0.1f, -40.1f, 0.1f}, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, 2DBinaryOpF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto b = builder.ConstantR2({{-1.5f, 8.14f, 42.0}, {-1.0f, -4.0f, 5.55f}}); - auto add = builder.Add(a, b); + builder.Add(a, b); Array2D expected_array( {{-4.0f, 11.28f, 43.0f}, {1.25f, -14.0f, 8.88f}}); @@ -2311,11 +2308,11 @@ XLA_TEST_F(ArrayElementwiseOpTest, 2DBinaryOpF32s) { XLA_TEST_F(ArrayElementwiseOpTest, ScalarPlus2DF32) { // Add a scalar + matrix. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto scalar = builder.ConstantR0(3.0f); - auto add = builder.Add(scalar, a); + builder.Add(scalar, a); Array2D expected_array({{0.5f, 6.14f, 4.0f}, {5.25f, -7.0f, 6.33f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); @@ -2323,11 +2320,11 @@ XLA_TEST_F(ArrayElementwiseOpTest, ScalarPlus2DF32) { XLA_TEST_F(ArrayElementwiseOpTest, 2DPlusScalarF32) { // Add a matrix + scalar. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto scalar = builder.ConstantR0(3.0f); - auto add = builder.Add(a, scalar); + builder.Add(a, scalar); Array2D expected_array({{0.5f, 6.14f, 4.0f}, {5.25f, -7.0f, 6.33f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); @@ -2336,14 +2333,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, 2DPlusScalarF32) { XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo2DF32) { // Test simple broadcasting of a R1F32 over R2F32. The vector's size matches // only dim 0 of the matrix. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({20.0f, 40.0f, 60.0f}); // clang-format off auto m = builder.ConstantR2({ {-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); // clang-format on - auto add = builder.Add(v, m, /*broadcast_dimensions=*/{1}); + builder.Add(v, m, /*broadcast_dimensions=*/{1}); Array2D expected_array( {{17.5f, 43.14f, 61.0f}, {22.25f, 30.0f, 63.33f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); @@ -2369,10 +2366,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Eq) { XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Ne) { // Test broadcasting in Ne comparison. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({42, 73}); auto m = builder.ConstantR2({{42, 73}, {42, 52}}); - auto cmp = builder.Ne(v, m, /*broadcast_dimensions=*/{1}); + builder.Ne(v, m, /*broadcast_dimensions=*/{1}); const string expected = R"(pred[2,2] { { 00 }, @@ -2383,10 +2380,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Ne) { XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Ge) { // Test broadcasting in Ge comparison. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({1, 2, 3, 4}); auto m = builder.ConstantR2({{1, 0, 5, 6}, {42, 52, 10, 4}}); - auto cmp = builder.Ge(v, m, /*broadcast_dimensions=*/{1}); + builder.Ge(v, m, /*broadcast_dimensions=*/{1}); const string expected = R"(pred[2,4] { { 1100 }, @@ -2397,10 +2394,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Ge) { XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Gt) { // Test broadcasting in Gt comparison. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({1, 2, 3, 4}); auto m = builder.ConstantR2({{1, 0, 5, 6}, {42, 52, 10, 4}}); - auto cmp = builder.Gt(v, m, /*broadcast_dimensions=*/{1}); + builder.Gt(v, m, /*broadcast_dimensions=*/{1}); const string expected = R"(pred[2,4] { { 0100 }, @@ -2411,10 +2408,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Gt) { XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Le) { // Test broadcasting in Le comparison. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({1, 2, 3, 4}); auto m = builder.ConstantR2({{1, 0, 5, 6}, {42, 52, 10, 4}}); - auto cmp = builder.Le(v, m, /*broadcast_dimensions=*/{1}); + builder.Le(v, m, /*broadcast_dimensions=*/{1}); const string expected = R"(pred[2,4] { { 1011 }, @@ -2425,10 +2422,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Le) { XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Lt) { // Test broadcasting in Lt comparison. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({1, 2, 3, 4}); auto m = builder.ConstantR2({{1, 0, 5, 6}, {42, 52, 10, 4}}); - auto cmp = builder.Lt(v, m, /*broadcast_dimensions=*/{1}); + builder.Lt(v, m, /*broadcast_dimensions=*/{1}); const string expected = R"(pred[2,4] { { 0011 }, @@ -2440,24 +2437,24 @@ XLA_TEST_F(ArrayElementwiseOpTest, Compare1DTo2DS32Lt) { XLA_TEST_F(ArrayElementwiseOpTest, Mul2Dby1DF32) { // Test simple broadcasting of a R1F32 over R2F32 when the order of binary op // arguments is reversed. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto m = builder.ConstantR2({{1.5f, 2.5f, 3.5f}, {4.5f, 5.5f, 6.5f}}); auto v = builder.ConstantR1({2.0f, 4.0f, 6.0f}); - auto add = builder.Mul(m, v, /*broadcast_dimensions=*/{1}); + builder.Mul(m, v, /*broadcast_dimensions=*/{1}); Array2D expected_array({{3.0f, 10.0f, 21.0f}, {9.0f, 22.0f, 39.0f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo2DWithDegenerateDim1) { // Tests broadcasting for arrays with degenerate (size == 1) dimensions. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // m's shape in XLA notation is {3, 2} // md's shape in XLA notation is {3, 1} // The result has shape {3, 2}, where md is broadcast over m auto m = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto md = builder.ConstantR2({{10.0f, 20.0f, 30.0f}}); - auto add = builder.Add(m, md); + builder.Add(m, md); Array2D expected_array( {{7.5f, 23.14f, 31.0f}, {12.25f, 10.0f, 33.33f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); @@ -2465,14 +2462,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo2DWithDegenerateDim1) { XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo2DWithDegenerateDim0) { // Tests broadcasting for arrays with degenerate (size == 1) dimensions. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // m's shape in XLA notation is {3, 2} // md's shape in XLA notation is {1, 2} // The result has shape {3, 2}, where md is broadcast over m auto m = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto md = builder.ConstantR2({{10.0f}, {20.0f}}); - auto add = builder.Add(m, md); + builder.Add(m, md); Array2D expected_array( {{7.5f, 13.14f, 11.0f}, {22.25f, 10.0f, 23.33f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); @@ -2483,13 +2480,13 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add2DsWithDegenerateDimsOuterProduct) { // effectively creates an "outer product" operation. // This is taken from the Numpy docs example at: // http://docs.scipy.org/doc/numpy-1.10.1/user/basics.broadcasting.html - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // a's shape in XLA notation is {1, 4} // b's shape in XLA notation is {3, 1} // The result has shape {3, 4}. auto a = builder.ConstantR2({{0.0f}, {10.0f}, {20.0f}, {30.0f}}); auto b = builder.ConstantR2({{1.0f, 2.0f, 3.0f}}); - auto add = builder.Add(a, b); + builder.Add(a, b); Array2D expected_array({{1.0f, 2.0f, 3.0f}, {11.0f, 12.0f, 13.0f}, {21.0f, 22.0f, 23.0f}, @@ -2500,10 +2497,10 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add2DsWithDegenerateDimsOuterProduct) { XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo2DF32TwoWaysOver1) { // Add together a (2,2) array and a (2) array, using dimension 0 for // broadcasting (though there are two ways to broadcast these shapes). - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({20.0f, 40.0f}); auto m = builder.ConstantR2({{10.0f, 50.0f}, {77.0f, 88.0f}}); - auto add = builder.Add(v, m, /*broadcast_dimensions=*/{1}); + builder.Add(v, m, /*broadcast_dimensions=*/{1}); Array2D expected_array({{30.0f, 90.0f}, {97.0f, 128.0f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); } @@ -2511,17 +2508,17 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo2DF32TwoWaysOver1) { XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo2DF32TwoWaysOver0) { // Add together a (2,2) array and a (2) array, using dimension 1 for // broadcasting (though there are two ways to broadcast these shapes). - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto v = builder.ConstantR1({20.0f, 40.0f}); auto m = builder.ConstantR2({{10.0f, 50.0f}, {77.0f, 88.0f}}); - auto add = builder.Add(v, m, /*broadcast_dimensions=*/{0}); + builder.Add(v, m, /*broadcast_dimensions=*/{0}); Array2D expected_array({{30.0f, 70.0f}, {117.0f, 128.0f}}); ComputeAndCompareR2(&builder, expected_array, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, 3DBinaryOpF32s) { // Binary add of two R3s together - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); Array3D a_3d({{{1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}}, {{7.0f, 8.0f}, {9.0f, 10.0f}, {11.0f, 12.0f}}}); auto a = builder.ConstantR3FromArray3D(a_3d); @@ -2529,7 +2526,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, 3DBinaryOpF32s) { Array3D b_3d({{{2.0f, 4.0f}, {6.0f, 8.0f}, {10.0f, 12.0f}}, {{14.0f, 16.0f}, {18.0f, 20.0f}, {22.0f, 24.0f}}}); auto b = builder.ConstantR3FromArray3D(b_3d); - auto add = builder.Add(a, b); + builder.Add(a, b); Array3D expected_3d( {{{3.0f, 6.0f}, {9.0f, 12.0f}, {15.0f, 18.0f}}, @@ -2540,7 +2537,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, 3DBinaryOpF32s) { XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver2) { // Add together a (2, 3, 2) array with a (2) array, using dimension 0 for // broadcasting (though there are two ways to broadcast these shapes). - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // clang-format off Array3D a_3d({ {{1.0f, 2.0f}, @@ -2553,7 +2550,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver2) { // clang-format on auto a = builder.ConstantR3FromArray3D(a_3d); auto v = builder.ConstantR1({10.0f, 20.0f}); - auto add = builder.Add(a, v, /*broadcast_dimensions=*/{2}); + builder.Add(a, v, /*broadcast_dimensions=*/{2}); Array3D expected_3d( {{{11.0f, 22.0f}, {13.0f, 24.0f}, {15.0f, 26.0f}}, @@ -2564,7 +2561,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver2) { XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver0) { // Add together a (2, 3, 2) array with a (2) array, using dimension 2 for // broadcasting (though there are two ways to broadcast these shapes). - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // clang-format off Array3D a_3d({ {{1.0f, 2.0f}, @@ -2577,7 +2574,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver0) { // clang-format on auto a = builder.ConstantR3FromArray3D(a_3d); auto v = builder.ConstantR1({10.0f, 20.0f}); - auto add = builder.Add(a, v, /*broadcast_dimensions=*/{0}); + builder.Add(a, v, /*broadcast_dimensions=*/{0}); // clang-format off Array3D expected_3d({ @@ -2595,7 +2592,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add1DTo3DTwoWaysOver0) { XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo3D) { // Add together a (2, 3, 2) array with a (3, 2) array, using dimensions {1,2} // for broadcasting. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); // clang-format off Array3D a_3d({ {{1.0f, 2.0f}, @@ -2610,7 +2607,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo3D) { {10.0f, 20.0f, 30.0f}, {40.0f, 50.0f, 60.0f}, }); - auto add = builder.Add(a, m, /*broadcast_dimensions=*/{0, 1}); + builder.Add(a, m, /*broadcast_dimensions=*/{0, 1}); Array3D expected_3d({ {{11.0f, 12.0f}, @@ -2627,7 +2624,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, Add2DTo3D) { XLA_TEST_F(ArrayElementwiseOpTest, CompareGtR3F32sWithDegenerateDim2) { // Comparison between two 3D arrays of compatible shapes: // (2, 3, 2) and (2, 3, 1): expected to produce a (2, 3, 2) shape of PREDs. - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); Array3D a_3d({{{1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}}, {{7.0f, 8.0f}, {9.0f, 10.0f}, {11.0f, 12.0f}}}); auto a = builder.ConstantR3FromArray3D(a_3d); @@ -2651,7 +2648,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, CompareGtR3F32sWithDegenerateDim2) { } XLA_TEST_F(ArrayElementwiseOpTest, 4DBinaryOpF32s) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr> operand_a_4d(new Array4D(2, 3, 4, 5)); std::unique_ptr> operand_b_4d(new Array4D(2, 3, 4, 5)); @@ -2672,13 +2669,13 @@ XLA_TEST_F(ArrayElementwiseOpTest, 4DBinaryOpF32s) { auto a = builder.ConstantR4FromArray4D(*operand_a_4d); auto b = builder.ConstantR4FromArray4D(*operand_b_4d); - auto add = builder.Add(a, b); + builder.Add(a, b); ComputeAndCompareR4(&builder, *expected_4d, {}, error_spec_); } XLA_TEST_F(ArrayElementwiseOpTest, R4PlusR1InDim1) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr> operand_a_4d(new Array4D(2, 3, 4, 5)); std::unique_ptr> expected_4d(new Array4D(2, 3, 4, 5)); @@ -2700,7 +2697,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, R4PlusR1InDim1) { auto a = builder.ConstantR4FromArray4D(*operand_a_4d); auto b = builder.ConstantR1(operand_b_1d); - auto add = builder.Add(a, b, {1}); + builder.Add(a, b, {1}); ComputeAndCompareR4(&builder, *expected_4d, {}, error_spec_); } @@ -2715,7 +2712,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, R4_16x16x2x2_Plus_R1_16) { std::vector r1(d1); std::iota(r1.begin(), r1.end(), 1.0); - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); std::unique_ptr a_literal = Literal::CreateR4FromArray4DWithLayout( r4, LayoutUtil::MakeLayout({0, 1, 2, 3})); auto a = builder.ConstantLiteral(*a_literal); @@ -2736,11 +2733,11 @@ XLA_TEST_F(ArrayElementwiseOpTest, R4_16x16x2x2_Plus_R1_16) { // Show that we can't add two opaques. XLA_TEST_F(ArrayElementwiseOpTest, CannotAddOpaques) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto shape = ShapeUtil::MakeOpaqueShape(); auto x = builder.Parameter(0, shape, "x"); - auto concatenated = builder.Add(x, x); - StatusOr computation_status = builder.Build(); + builder.Add(x, x); + auto computation_status = builder.Build(); ASSERT_FALSE(computation_status.ok()); EXPECT_THAT(computation_status.status().ToString(), ::testing::ContainsRegex( @@ -2748,12 +2745,12 @@ XLA_TEST_F(ArrayElementwiseOpTest, CannotAddOpaques) { } XLA_TEST_F(ArrayElementwiseOpTest, IdentityBroadcastOfSameRankIsAllowed) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto b = builder.ConstantR2({{-1.5f, 8.14f, 42.0}, {-1.0f, -4.0f, 5.55f}}); - auto add = builder.Add(a, b, /*broadcast_dimensions=*/{0, 1}); + builder.Add(a, b, /*broadcast_dimensions=*/{0, 1}); Array2D expected_array( {{-4.0f, 11.28f, 43.0f}, {1.25f, -14.0f, 8.88f}}); @@ -2761,14 +2758,14 @@ XLA_TEST_F(ArrayElementwiseOpTest, IdentityBroadcastOfSameRankIsAllowed) { } XLA_TEST_F(ArrayElementwiseOpTest, NonIdentityBroadcastOfSameRankIsDisallowed) { - ComputationBuilder builder(client_, TestName()); + XlaBuilder builder(TestName()); auto a = builder.ConstantR2({{-2.5f, 3.14f, 1.0f}, {2.25f, -10.0f, 3.33f}}); auto b = builder.ConstantR2({{-1.5f, 8.14f, 42.0}, {-1.0f, -4.0f, 5.55f}}); - auto add = builder.Add(a, b, /*broadcast_dimensions=*/{1, 0}); + builder.Add(a, b, /*broadcast_dimensions=*/{1, 0}); - StatusOr computation_status = builder.Build(); + auto computation_status = builder.Build(); ASSERT_FALSE(computation_status.ok()); EXPECT_THAT(computation_status.status().error_message(), ::testing::ContainsRegex("must.*be the identity")); diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.cc b/tensorflow/compiler/xla/tests/client_library_test_base.cc index d9bd1ce6eb..ec95a68ead 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.cc +++ b/tensorflow/compiler/xla/tests/client_library_test_base.cc @@ -139,14 +139,31 @@ std::unique_ptr ClientLibraryTestBase::ExecuteAndTransferOrDie( return ExecuteAndTransfer(builder, arguments).ConsumeValueOrDie(); } +string ClientLibraryTestBase::ExecuteToString( + XlaBuilder* builder, tensorflow::gtl::ArraySlice arguments) { + auto computation_status = builder->Build(); + if (!computation_status.ok()) { + return computation_status.status().ToString(); + } + auto computation = computation_status.ConsumeValueOrDie(); + + auto result = + client_->ExecuteAndTransfer(computation, arguments, &execution_options_); + if (!result.ok()) { + return result.status().ToString(); + } else { + return result.ValueOrDie()->ToString(); + } +} + string ClientLibraryTestBase::ExecuteToString( ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments) { - StatusOr computation_status = builder->Build(); + auto computation_status = builder->Build(); if (!computation_status.ok()) { return computation_status.status().ToString(); } - Computation computation = computation_status.ConsumeValueOrDie(); + auto computation = computation_status.ConsumeValueOrDie(); auto result = client_->ExecuteAndTransfer(computation, arguments, &execution_options_); diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.h b/tensorflow/compiler/xla/tests/client_library_test_base.h index c39597c4e1..5ff200be03 100644 --- a/tensorflow/compiler/xla/tests/client_library_test_base.h +++ b/tensorflow/compiler/xla/tests/client_library_test_base.h @@ -124,6 +124,8 @@ class ClientLibraryTestBase : public ::testing::Test { // Run a computation and return its value as a string. If an error // occurs, then instead return the error as a string. + string ExecuteToString(XlaBuilder* builder, + tensorflow::gtl::ArraySlice arguments); string ExecuteToString(ComputationBuilder* builder, tensorflow::gtl::ArraySlice arguments); -- GitLab From 071e32d7334b0ff6452111c83ae0b139f28b36ff Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 19:29:48 -0700 Subject: [PATCH 246/906] Add "distribute" argument to tf.estimator.RunConfig() in anticipation of upcoming DistributionStrategy support in Estimator. PiperOrigin-RevId: 190563074 --- tensorflow/python/estimator/run_config.py | 20 +++++++++++++++---- .../tensorflow.estimator.-run-config.pbtxt | 6 +++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/estimator/run_config.py b/tensorflow/python/estimator/run_config.py index 820fda7765..141eaeff64 100644 --- a/tensorflow/python/estimator/run_config.py +++ b/tensorflow/python/estimator/run_config.py @@ -43,7 +43,8 @@ _DEFAULT_REPLACEABLE_LIST = [ 'session_config', 'keep_checkpoint_max', 'keep_checkpoint_every_n_hours', - 'log_step_count_steps' + 'log_step_count_steps', + 'distribute' ] _SAVE_CKPT_ERR = ( @@ -300,7 +301,8 @@ class RunConfig(object): session_config=None, keep_checkpoint_max=5, keep_checkpoint_every_n_hours=10000, - log_step_count_steps=100): + log_step_count_steps=100, + distribute=None): """Constructs a RunConfig. All distributed training related properties `cluster_spec`, `is_chief`, @@ -424,7 +426,10 @@ class RunConfig(object): the feature. log_step_count_steps: The frequency, in number of global steps, that the global step/sec and the loss will be logged during training. - + distribute: an optional instance of + `tf.contrib.distribute.DistributionStrategy`. If specified, + then Estimator will distribute the user's model according to the policy + specified by that strategy. Raises: ValueError: If both `save_checkpoints_steps` and `save_checkpoints_secs` @@ -460,7 +465,8 @@ class RunConfig(object): session_config=session_config, keep_checkpoint_max=keep_checkpoint_max, keep_checkpoint_every_n_hours=keep_checkpoint_every_n_hours, - log_step_count_steps=log_step_count_steps) + log_step_count_steps=log_step_count_steps, + distribute=distribute) self._init_distributed_setting_from_environment_var(tf_config) @@ -671,6 +677,12 @@ class RunConfig(object): """Returns the platform defined (in TF_CONFIG) service dict.""" return self._service + @property + def distribute(self): + """Returns the optional `tf.contrib.distribute.DistributionStrategy` object. + """ + return self._distribute + def replace(self, **kwargs): """Returns a new instance of `RunConfig` replacing specified properties. diff --git a/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt b/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt index 091b1be0c8..759ff752b0 100644 --- a/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt @@ -6,6 +6,10 @@ tf_class { name: "cluster_spec" mtype: "" } + member { + name: "distribute" + mtype: "" + } member { name: "evaluation_master" mtype: "" @@ -80,7 +84,7 @@ tf_class { } member_method { name: "__init__" - argspec: "args=[\'self\', \'model_dir\', \'tf_random_seed\', \'save_summary_steps\', \'save_checkpoints_steps\', \'save_checkpoints_secs\', \'session_config\', \'keep_checkpoint_max\', \'keep_checkpoint_every_n_hours\', \'log_step_count_steps\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'100\', \'\', \'\', \'None\', \'5\', \'10000\', \'100\'], " + argspec: "args=[\'self\', \'model_dir\', \'tf_random_seed\', \'save_summary_steps\', \'save_checkpoints_steps\', \'save_checkpoints_secs\', \'session_config\', \'keep_checkpoint_max\', \'keep_checkpoint_every_n_hours\', \'log_step_count_steps\', \'distribute\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'100\', \'\', \'\', \'None\', \'5\', \'10000\', \'100\', \'None\'], " } member_method { name: "replace" -- GitLab From 41982886efaa2ab9cc75d0d5ab6c27368468d061 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 19:30:26 -0700 Subject: [PATCH 247/906] Fix inconsistency in run_cond. PiperOrigin-RevId: 190563114 --- .../contrib/autograph/converters/ifexp.py | 2 +- .../autograph/utils/multiple_dispatch.py | 11 +++++++++-- .../autograph/utils/multiple_dispatch_test.py | 17 ++++++++--------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tensorflow/contrib/autograph/converters/ifexp.py b/tensorflow/contrib/autograph/converters/ifexp.py index aff94d2b79..bb0c0a36a7 100644 --- a/tensorflow/contrib/autograph/converters/ifexp.py +++ b/tensorflow/contrib/autograph/converters/ifexp.py @@ -27,7 +27,7 @@ class IfExp(transformer.Base): def visit_IfExp(self, node): template = """ - autograph_utils.run_cond(test, lambda: body, lambda: orelse) + autograph_utils.run_cond(test, lambda: (body,), lambda: (orelse,)) """ desugared_ifexp = templates.replace_as_expression( template, test=node.test, body=node.body, orelse=node.orelse) diff --git a/tensorflow/contrib/autograph/utils/multiple_dispatch.py b/tensorflow/contrib/autograph/utils/multiple_dispatch.py index b756ccfaee..47049255f3 100644 --- a/tensorflow/contrib/autograph/utils/multiple_dispatch.py +++ b/tensorflow/contrib/autograph/utils/multiple_dispatch.py @@ -55,10 +55,17 @@ def run_cond(condition, true_fn, false_fn): def py_cond(condition, true_fn, false_fn): + """Functional version of Python's conditional.""" if condition: - return true_fn() + results = true_fn() else: - return false_fn() + results = false_fn() + + # The contract for the branch functions is to return tuples, but they should + # be collapsed to a single element when there is only one output. + if len(results) == 1: + return results[0] + return results def run_while(cond_fn, body_fn, init_args): diff --git a/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py b/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py index 8c7daa6ded..e6a41bb416 100644 --- a/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py +++ b/tensorflow/contrib/autograph/utils/multiple_dispatch_test.py @@ -56,20 +56,19 @@ class MultipleDispatchTest(test.TestCase): self.assertFalse(should_be_false2) def test_run_cond_python(self): - true_fn = lambda: 2.0 - false_fn = lambda: 3.0 - self.assertEqual(multiple_dispatch.run_cond(True, true_fn, false_fn), 2.0) - self.assertEqual(multiple_dispatch.run_cond(False, true_fn, false_fn), 3.0) + true_fn = lambda: (2,) + false_fn = lambda: (3,) + self.assertEqual(multiple_dispatch.run_cond(True, true_fn, false_fn), 2) + self.assertEqual(multiple_dispatch.run_cond(False, true_fn, false_fn), 3) def test_run_cond_tf(self): - - true_fn = lambda: constant([2.0]) - false_fn = lambda: constant([3.0]) + true_fn = lambda: (constant(2),) + false_fn = lambda: (constant(3),) with Session() as sess: out = multiple_dispatch.run_cond(constant(True), true_fn, false_fn) - self.assertEqual(sess.run(out), 2.0) + self.assertEqual(sess.run(out), 2) out = multiple_dispatch.run_cond(constant(False), true_fn, false_fn) - self.assertEqual(sess.run(out), 3.0) + self.assertEqual(sess.run(out), 3) def test_run_while_python(self): cond_fn = lambda x, t, s: x > t -- GitLab From 591b6a7709fa05d490b0c718253492dfad35557f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 19:46:51 -0700 Subject: [PATCH 248/906] Include additional cases for evaluating the fqn annotation. PiperOrigin-RevId: 190564036 --- .../contrib/autograph/pyct/static_analysis/live_values.py | 8 +++++++- .../autograph/pyct/static_analysis/live_values_test.py | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py b/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py index 5f813355e6..53ae154590 100644 --- a/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/live_values.py @@ -59,9 +59,15 @@ class LiveValueResolver(transformer.Base): obj = self.context.namespace[node.id] anno.setanno(node, 'live_val', obj) if hasattr(obj, '__name__'): + anno.setanno(node, 'fqn', (obj.__name__,)) + elif hasattr(obj, '__class__'): + obj_class = obj.__class__ + anno.setanno(node, 'fqn', + (obj_class.__module__, obj_class.__name__)) + else: # If the symbol value is for example a primitive, then it will not # have a name. - anno.setanno(node, 'fqn', (obj.__name__,)) + pass else: pass # TODO(mdan): Should we raise an error here? diff --git a/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py b/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py index b66439624e..69e428bde1 100644 --- a/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py +++ b/tensorflow/contrib/autograph/pyct/static_analysis/live_values_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import six + from tensorflow.contrib.autograph.pyct import anno from tensorflow.contrib.autograph.pyct import context from tensorflow.contrib.autograph.pyct import parser @@ -75,7 +77,11 @@ class LiveValuesResolverTest(test.TestCase): node = self._parse_and_analyze(test_fn, {'a': True}) retval_node = node.body[0].body[0].value - self.assertFalse(anno.hasanno(retval_node, 'fqn')) + if six.PY2: + self.assertEqual( + anno.getanno(retval_node, 'fqn'), ('__builtin__', 'bool')) + else: + self.assertEqual(anno.getanno(retval_node, 'fqn'), ('builtins', 'bool')) def test_namespace(self): -- GitLab From c6bc514ffcc601abb7018721c2518cf91a39eeb1 Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Mon, 26 Mar 2018 19:53:09 -0700 Subject: [PATCH 249/906] Updating documentation of supported ops. PiperOrigin-RevId: 190564365 --- .../lite/g3doc/tf_ops_compatibility.md | 183 +++++++++++++++++- 1 file changed, 175 insertions(+), 8 deletions(-) diff --git a/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md b/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md index b1bbb7c670..61ea5231e3 100644 --- a/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md +++ b/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md @@ -30,13 +30,18 @@ quantized training is necessary before conversion. ## Data Format and Broadcasting At the moment TensorFlow Lite supports only TensorFlow's "NHWC" format, and -broadcasting in operations like tf.add and tf.mul is generally not supported. +broadcasting is only support in a limited number of ops (tf.add, tf.mul, tf.sub, +and tf.div). ## Compatible Operations The following TensorFlow operations are usually mapped to their TensorFlow Lite counterparts: +* [tf.batch_to_space_nd](https://www.tensorflow.org/api_docs/python/tf/batch_to_space_nd) - + *as long as the input tensor is 4D (1 batch + 2 spatial + 1 other) and the + crops attribute is not used* +* [tf.exp](https://www.tensorflow.org/api_docs/python/tf/exp) * [tf.matmul](https://www.tensorflow.org/api_docs/python/tf/matmul) - *as long as the second argument is constant and transposition is not used* * [tf.nn.avg_pool](https://www.tensorflow.org/api_docs/python/tf/nn/avg_pool) @@ -47,12 +52,30 @@ counterparts: * [tf.nn.l2_normalize](https://www.tensorflow.org/api_docs/python/tf/nn/l2_normalize) - *as long as normalization is done along the last dimension* * [tf.nn.local_response_normalization](https://www.tensorflow.org/api_docs/python/tf/nn/local_response_normalization) +* [tf.nn.log_softmax](https://www.tensorflow.org/api_docs/python/tf/nn/log_softmax) - + *as long as axis is not provided* * [tf.nn.max_pool](https://www.tensorflow.org/api_docs/python/tf/nn/max_pool) * [tf.nn.softmax](https://www.tensorflow.org/api_docs/python/tf/nn/softmax) - *as long as tensors are 2D and axis is the last dimension* +* [tf.nn.top_k](https://www.tensorflow.org/api_docs/python/tf/nn/top_k) +* [tf.pad](https://www.tensorflow.org/api_docs/python/tf/pad) - *as long as + mode and constant_values are not used* +* [tf.reduce_mean](https://www.tensorflow.org/api_docs/python/tf/reduce_mean) - + *as long as the reduction_indices attribute is not used* * [tf.reshape](https://www.tensorflow.org/api_docs/python/tf/reshape) * [tf.sigmoid](https://www.tensorflow.org/api_docs/python/tf/sigmoid) +* [tf.space_to_batch_nd](https://www.tensorflow.org/api_docs/python/tf/space_to_batch_nd) - + *as long as the input tensor is 4D (1 batch + 2 spatial + 1 other)* * [tf.space_to_depth](https://www.tensorflow.org/api_docs/python/tf/space_to_depth) +* [tf.split](https://www.tensorflow.org/api_docs/python/tf/split) - *as long + as num is not provided and num_or_size_split contains number of splits as a + 0D tensor* +* [tf.squeeze](https://www.tensorflow.org/api_docs/python/tf/squeeze) - *as + long as axis is not provided* +* [tf.strided_slice](https://www.tensorflow.org/api_docs/python/tf/strided_slice) - + *as long as ellipsis_mask and new_axis_mask are not used* +* [tf.transpose](https://www.tensorflow.org/versions/master/api_docs/python/tf/transpose) - + *as long as conjugate is not used* ## Straightforward Conversions, Constant-Folding and Fusing @@ -91,7 +114,6 @@ Here is a list of TensorFlow operations that are usually removed from the graph: * [tf.shape](https://www.tensorflow.org/api_docs/python/tf/shape) * [tf.sqrt](https://www.tensorflow.org/api_docs/python/tf/sqrt) * [tf.square](https://www.tensorflow.org/api_docs/python/tf/square) -* [tf.squeeze](https://www.tensorflow.org/api_docs/python/tf/squeeze) * [tf.subtract](https://www.tensorflow.org/api_docs/python/tf/subtract) * [tf.tile](https://www.tensorflow.org/api_docs/python/tf/tile) * [tf.nn.batch_norm_with_global_normalization](https://www.tensorflow.org/api_docs/python/tf/nn/batch_norm_with_global_normalization) @@ -109,17 +131,11 @@ fused. TensorFlow operation not listed above are likely unsupported. Notably, the following common ops are not supported at the moment: -* [tf.batch_to_space_nd](https://www.tensorflow.org/api_docs/python/tf/batch_to_space_nd) * [tf.depth_to_space](https://www.tensorflow.org/api_docs/python/tf/depth_to_space) * [tf.floor](https://www.tensorflow.org/api_docs/python/tf/floor) * [tf.gather](https://www.tensorflow.org/api_docs/python/tf/gather) * [tf.image.resize_bilinear](https://www.tensorflow.org/api_docs/python/tf/image/resize_bilinear) -* [tf.pad](https://www.tensorflow.org/api_docs/python/tf/pad) -* [tf.reduce_mean](https://www.tensorflow.org/api_docs/python/tf/reduce_mean) * [tf.slice](https://www.tensorflow.org/api_docs/python/tf/slice) -* [tf.space_to_batch_nd](https://www.tensorflow.org/api_docs/python/tf/space_to_batch_nd) -* [tf.split](https://www.tensorflow.org/api_docs/python/tf/split) -* [tf.strided_slice](https://www.tensorflow.org/api_docs/python/tf/strided_slice) * [tf.tanh](https://www.tensorflow.org/api_docs/python/tf/tanh) ## TensorFlow Lite Operations @@ -160,6 +176,20 @@ Options { } ``` +**BATCH_TO_SPACE_ND** + +``` +Inputs { + 0: 4D tensor + 1: 1D tensor + 2: 2D tensor +} +Outputs { + 0: tensor rearranged using block_shape. See tf.batch_to_space_nd for + details. +} +``` + **CONCATENATION** ``` @@ -213,6 +243,17 @@ Options { } ``` +**EXP** + +``` +Inputs { + 0: tensor +} +Outputs { + 0: result of computing element-wise exponential of the input tensor +} +``` + **FULLY_CONNECTED** ``` @@ -289,6 +330,17 @@ Outputs { } ``` +**LOG_SOFTMAX** + +``` +Inputs { + 0: tensor +} +Outputs { + 0: tensor equivalent to logits - log(reduce_sum(exp(logits), -1)) +} +``` + **MAX_POOL_2D** ``` @@ -322,6 +374,34 @@ Options { } ``` +**PAD** + +``` +Inputs { + 0: tensor + 1: tensor +} +Outputs { + 0: tensor where additional values are added before and after the contents of + each dimension +} +``` + +**MEAN (tf.reduce_mean)** + +``` +Inputs { + 0: tensor + 1: tensor +} +Outputs { + 0: tensor containing the mean of the elements +} +Options { + keep_dims: whether to retain reduced dimensions +} +``` + **RELU** ``` @@ -399,6 +479,93 @@ Options { } ``` +**SPACE_TO_BATCH_ND** + +``` +Inputs { + 0: 4D tensor + 1: 1D tensor + 2: 2D tensor +} +Outputs { + 0: a tensor rearranged using block_shape. See tf.space_to_batch_nd for + details. +} +``` + +**SPLIT** + +``` +Inputs { + 0: 0D tensor (axis) + 1: tensor (input) +} +Outputs { + 0-N: subtensors built from the input tensors +} +Options { + num_splits: Specifies number of outputs +} +``` + +**SQUEEZE** + +``` +Inputs { + 0: tensor +} +Outputs { + 0: tensor without any dimensions of size 1 +} +Options { + squeeze_dims +} +``` + +**STRIDED_SLICE** + +``` +Inputs { + 0: tensor + 1: 1D tensor + 2: 1D tensor + 3: 1D tensor +} +Outputs { + 0: slice of the input tensor of the given size +} +Options { + begin_mask: mask for begin indicies + end_mask: mask for end indices + shrink_axis_mask: mask that indicates which dimensions to remove +} +``` + +**TOP_K** + +``` +Inputs { + 0: tensor + 1: OD tensor +} +Outputs { + 0: k largest element along each last dimensional slice + 1: indicies of values within the last dimension of the input ensor +} +``` + +**TRANSPOSE** + +``` +Inputs { + 0: tensor + 1: tensor +} +Outputs { + 0: tensor permuted according to perm +} +``` + And these are TensorFlow Lite operations that are present but not ready for custom models yet: -- GitLab From 63cfd006fa1e848daeaf9ac74e2c9f8c42e401b1 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 20:01:35 -0700 Subject: [PATCH 250/906] Do not assume Attribute nodes always have a QN - it may be missing for attributes of dynamic objects, like function calls. PiperOrigin-RevId: 190564784 --- tensorflow/contrib/autograph/pyct/ast_util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/autograph/pyct/ast_util.py b/tensorflow/contrib/autograph/pyct/ast_util.py index 5a41b5e4a9..4f76a69522 100644 --- a/tensorflow/contrib/autograph/pyct/ast_util.py +++ b/tensorflow/contrib/autograph/pyct/ast_util.py @@ -84,7 +84,10 @@ class SymbolRenamer(gast.NodeTransformer): return self._process(node) def visit_Attribute(self, node): - return self._process(node) + if anno.hasanno(node, anno.Basic.QN): + return self._process(node) + # Attributes of dynamic objects will not have a QN. + return self.generic_visit(node) def rename_symbols(node, name_map): -- GitLab From 8bcc574711b8770e8341f77d1a9b8370d72d7477 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 20:02:24 -0700 Subject: [PATCH 251/906] Include subscripts in the list of nodes accepted for replacement. PiperOrigin-RevId: 190564824 --- tensorflow/contrib/autograph/pyct/templates.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tensorflow/contrib/autograph/pyct/templates.py b/tensorflow/contrib/autograph/pyct/templates.py index fb99e0d4e5..baf7923fff 100644 --- a/tensorflow/contrib/autograph/pyct/templates.py +++ b/tensorflow/contrib/autograph/pyct/templates.py @@ -95,6 +95,15 @@ class ReplaceTransformer(gast.NodeTransformer): self._check_inner_children_have_context(e) for e in node.values: self._check_inner_children_have_context(e) + elif isinstance(node, gast.Subscript): + self._check_inner_children_have_context(node.value) + self._check_inner_children_have_context(node.slice) + elif isinstance(node, gast.Slice): + self._check_inner_children_have_context(node.lower) + if node.upper: + self._check_inner_children_have_context(node.upper) + if node.step: + self._check_inner_children_have_context(node.step) elif isinstance(node, gast.Name): self._check_has_context(node) elif isinstance(node, (gast.Str, gast.Num)): @@ -127,6 +136,9 @@ class ReplaceTransformer(gast.NodeTransformer): self._check_inner_children_have_context(e) for e in node.values: self._check_inner_children_have_context(e) + elif isinstance(node, gast.Subscript): + self._set_inner_child_context(node.value, ctx) + self._check_inner_children_have_context(node.slice) elif isinstance(node, (gast.Str, gast.Num)): pass else: -- GitLab From 2f208bb73054109390ef9565e8038c14329f73ad Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Mon, 26 Mar 2018 20:18:03 -0700 Subject: [PATCH 252/906] [XLA] Add tests for R1 PRED Slices. PiperOrigin-RevId: 190566036 --- tensorflow/compiler/xla/tests/slice_test.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tensorflow/compiler/xla/tests/slice_test.cc b/tensorflow/compiler/xla/tests/slice_test.cc index fe36df160d..a14a365bd0 100644 --- a/tensorflow/compiler/xla/tests/slice_test.cc +++ b/tensorflow/compiler/xla/tests/slice_test.cc @@ -193,7 +193,9 @@ class SliceR1Test : public ClientLibraryTestBase, protected: template void Run(const R1Spec& spec) { - std::vector input(spec.input_dim0); + // This can't be an std::vector, since you can't grab an ArraySlice of a + // vector. + tensorflow::gtl::InlinedVector input(spec.input_dim0); std::iota(input.begin(), input.end(), NativeT()); ComputationBuilder builder(client_, TestName()); @@ -201,7 +203,8 @@ class SliceR1Test : public ClientLibraryTestBase, builder.Slice(original, {spec.slice_start}, {spec.slice_limit}, {spec.slice_stride}); - std::vector expected; + // Ditto. + tensorflow::gtl::InlinedVector expected; for (int i = spec.slice_start; i < spec.slice_limit; i += spec.slice_stride) { expected.push_back(i); @@ -230,6 +233,8 @@ XLA_TEST_P(SliceR1Test, DoIt_U64) { Run(GetParam()); } XLA_TEST_P(SliceR1Test, DoIt_S64) { Run(GetParam()); } +XLA_TEST_P(SliceR1Test, DoIt_PRED) { Run(GetParam()); } + // Tests for R1 slice ops. // The format for each testcase is {input size, start, limit, stride}. // clang-format off -- GitLab From 574303015eb2b6fc4e002f5d2400c3e7f512ae82 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 21:30:56 -0700 Subject: [PATCH 253/906] Add class DistributionStrategy to python/training/, though not part of the exposed TF API. PiperOrigin-RevId: 190570489 --- tensorflow/python/BUILD | 13 + tensorflow/python/training/distribute.py | 1118 +++++++++++++++++ tensorflow/python/training/distribute_test.py | 104 ++ 3 files changed, 1235 insertions(+) create mode 100644 tensorflow/python/training/distribute.py create mode 100644 tensorflow/python/training/distribute_test.py diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 30ecc477f2..20d7e81045 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -2914,6 +2914,7 @@ py_library( ":variables", "//tensorflow/python/eager:backprop", "//tensorflow/python/eager:context", + "//tensorflow/python/ops/losses", "//third_party/py/numpy", "@six_archive//:six", ], @@ -2943,6 +2944,18 @@ py_test( ], ) +py_test( + name = "distribute_test", + size = "small", + srcs = ["training/distribute_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":client_testlib", + ":training", + ":variable_scope", + ], +) + py_test( name = "evaluation_test", size = "small", diff --git a/tensorflow/python/training/distribute.py b/tensorflow/python/training/distribute.py new file mode 100644 index 0000000000..9261e13230 --- /dev/null +++ b/tensorflow/python/training/distribute.py @@ -0,0 +1,1118 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Class DistributionStrategy, TowerContext, and supporting APIs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import threading + +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops.losses import losses_impl +from tensorflow.python.training import device_util +from tensorflow.python.util import nest + + +# ------------------------------------------------------------------------------ +# Internal API for setting the current thread mode as being either in a +# tower or cross-tower context for a particular distribution strategy. + + +class _ThreadMode(object): + + def __init__(self, dist, cross, tower): + self.distribution_strategy = dist + self.cross_tower_context = cross + self.tower_context = tower + + +class _CrossTowerThreadMode(_ThreadMode): + + def __init__(self, distribution_strategy): + _ThreadMode.__init__( + self, distribution_strategy, distribution_strategy, None) + + +class _InTowerThreadMode(_ThreadMode): + + def __init__(self, tower_ctx): + _ThreadMode.__init__( + self, tower_ctx.distribution_strategy, None, tower_ctx) + + +_per_thread_mode = threading.local() + + +def _push_per_thread_mode(context): + if not hasattr(_per_thread_mode, "stack"): + _per_thread_mode.stack = [] + _per_thread_mode.stack.append(context) + + +def _pop_per_thread_mode(): + _per_thread_mode.stack.pop(-1) + + +class _DefaultTowerThreadMode(_ThreadMode): + """Type of default value returned by `_get_per_thread_mode()`. + + Used when the thread-local stack is empty. + """ + + def __init__(self): + # _default_distribution_strategy and _default_tower_context are + # defined at the bottom of this file. + _ThreadMode.__init__( + self, _default_distribution_strategy, None, _default_tower_context) + + +def _get_per_thread_mode(): + try: + return _per_thread_mode.stack[-1] + except (AttributeError, IndexError): + # _default_tower_mode is defined at the bottom of this file. + return _default_tower_mode + + +# ------------------------------------------------------------------------------ +# Context tracking whether in a distribution.update() or .update_non_slot() +# call. + + +_update_device = threading.local() + + +def get_update_device(): + try: + return _update_device.current + except AttributeError: + return None + + +class UpdateContext(object): + """Context manager when you are in `update()` or `update_non_slot()`.""" + + def __init__(self, device): + self._device = device + self._old_device = None + + def __enter__(self): + self._old_device = get_update_device() + _update_device.current = self._device + + def __exit__(self, exception_type, exception_value, traceback): + del exception_type, exception_value, traceback + _update_device.current = self._old_device + + +# ------------------------------------------------------------------------------ +# Public API for accessing the current thread mode + + +def get_tower_context(): + """Returns the current TowerContext or None. + + Note that execution: + 1. starts in the default (single-tower) tower context; + 2. switches to cross-tower context when entering a + `with DistributionStrategy.scope():` block; + 3. switches to a (non-default) tower context inside + `call_for_each_tower(fn, ...)`; + 4. if `fn` calls `get_tower_context()->merge_call(merge_fn, ...)`, then + inside `merge_fn` you are back in the cross-tower context. + + Note that you can also go directly from step 1 to 4 to switch to a + cross-tower context for the default `DistributionStrategy`. You may + also switch from the cross-tower context of 4 to a tower context by + calling `call_for_each_tower()`, jumping back to step 3. + + Most `DistributionStrategy` methods may only be executed in + a cross-tower context, in a tower context you should use the + `TowerContext` API instead. + + Returns: + The current `TowerContext` object when in a tower context scope, else None. + + Exactly one of `get_tower_context()` and `get_cross_tower_context()` + will return None in a particular block. + """ + return _get_per_thread_mode().tower_context + + +def get_cross_tower_context(): + """Returns the current DistributionStrategy if in a cross-tower context. + + Note that execution: + 1. starts in the default (single-tower) tower context; + 2. switches to cross-tower context when entering a + `with DistributionStrategy.scope():` block; + 3. switches to a (non-default) tower context inside + `call_for_each_tower(fn, ...)`; + 4. if `fn` calls `get_tower_context()->merge_call(merge_fn, ...)`, then + inside `merge_fn` you are back in the cross-tower context. + + Note that you can also go directly from step 1 to 4 to switch to a + cross-tower context for the default `DistributionStrategy`. You may + also switch from the cross-tower context of 4 to a tower context by + calling `call_for_each_tower()`, jumping back to step 3. + + Most `DistributionStrategy` methods may only be executed in + a cross-tower context. + + Returns: + Returns the current `DistributionStrategy` object in a cross-tower + context, or None. + + Exactly one of `get_tower_context()` and `get_cross_tower_context()` + will return None in a particular block. + """ + return _get_per_thread_mode().cross_tower_context + + +def get_distribution_strategy(): + """Returns the current `DistributionStrategy` object. + + Returns: + A `DistributionStrategy` object. Inside a + `with distribution_strategy.scope()` block, it returns + `distribution_strategy`, otherwise it returns the default + (single-tower) `DistributionStrategy` object. + """ + return _get_per_thread_mode().distribution_strategy + + +def has_distribution_strategy(): + """Return if there is a current non-default `DistributionStrategy`. + + Returns: + True if inside a `with distribution_strategy.scope():`. + """ + return get_distribution_strategy() is not _default_distribution_strategy + + +# ------------------------------------------------------------------------------ +# Public utility functions. + + +def get_loss_reduction(): + """Reduce `method_string` corresponding to the last loss reduction.""" + loss_reduction = ops.get_default_graph()._last_loss_reduction # pylint: disable=protected-access + if loss_reduction == losses_impl.Reduction.SUM: + return "sum" + return "mean" + + +# ------------------------------------------------------------------------------ +# Internal API for validating the current thread mode + + +def _require_cross_tower_context(distribution_strategy): + """Verify in cross-tower context for `distribution_strategy`.""" + context = _get_per_thread_mode() + if context.cross_tower_context is distribution_strategy: return + # We have an error to report, figure out the right message. + if context.distribution_strategy is not distribution_strategy: + if context.distribution_strategy is _default_distribution_strategy: + raise RuntimeError( + 'Need to be inside "with distribution_strategy.scope()" for %s' % + (distribution_strategy,)) + else: + raise RuntimeError( + "Mixing different DistributionStrategy objects: %s is not %s" % + (context.distribution_strategy, distribution_strategy)) + assert context.cross_tower_context is None + raise RuntimeError("Method requires being in cross-tower context, use " + "get_tower_context().merge_call()") + + +def require_tower_context(tower_ctx): + """Verify in `tower_ctx` tower context.""" + context = _get_per_thread_mode() + if context.tower_context is tower_ctx: return + # We have an error to report, figure out the right message. + if context.tower_context is None: + raise RuntimeError("Need to be inside `call_for_each_tower()`") + if context.distribution_strategy is tower_ctx.distribution_strategy: + # Two different TowerContexts with the same DistributionStrategy. + raise RuntimeError("Mismatching tower context.") + raise RuntimeError( + "Mismatching DistributionStrategy objects: %s is not %s." % + (context.distribution_strategy, tower_ctx.distribution_strategy)) + + +def _require_distribution_strategy_scope(distribution_strategy): + """Verify in a `distribution_strategy.scope()` in this thread.""" + context = _get_per_thread_mode() + if context.distribution_strategy is distribution_strategy: return + # We have an error to report, figure out the right message. + if context.distribution_strategy is _default_distribution_strategy: + raise RuntimeError( + 'Need to be inside "with distribution_strategy.scope()" for %s' % + (distribution_strategy,)) + else: + raise RuntimeError( + "Mixing different DistributionStrategy objects: %s is not %s" % + (context.distribution_strategy, distribution_strategy)) + + +# ------------------------------------------------------------------------------ +# Internal context managers used to implement the DistributionStrategy +# base class + + +class _CurrentDistributionContext(object): + """Context manager for setting the `DistributionStrategy` and var creator.""" + + def __init__(self, distribution_strategy, var_creator_scope): + self._context = _CrossTowerThreadMode(distribution_strategy) + self._var_creator_scope = var_creator_scope + + def __enter__(self): + _push_per_thread_mode(self._context) + self._var_creator_scope.__enter__() + return self._context.distribution_strategy + + def __exit__(self, exception_type, exception_value, traceback): + self._var_creator_scope.__exit__(exception_type, exception_value, traceback) + _pop_per_thread_mode() + + +class _SameScopeAgainContext(object): + """Trivial context manager when you are already in `scope()`.""" + + def __init__(self, distribution_strategy): + self._distribution_strategy = distribution_strategy + + def __enter__(self): + return self._distribution_strategy + + def __exit__(self, exception_type, exception_value, traceback): + del exception_type, exception_value, traceback + + +# ------------------------------------------------------------------------------ +# Base classes for all distribution strategies. + + +class DistributionStrategy(object): + """A list of devices with a state & compute distribution policy. + + The intent is that you can write an algorithm in a stylized way and + it will be usable with a variety of different `DistributionStrategy` + implementations. Each descendant will implement a different strategy + for distributing the algorithm across multiple devices/machines. + Furthermore, these changes can be hidden inside the specific layers + and other library classes that need special treatment to run in a + distributed setting, so that most users' model definition code can + run unchanged. The `DistributionStrategy` API works the same way + with eager and graph execution. + + First let's introduce a few high-level concepts: + + * _Data parallelism_ is where we run multiple copies of the model + on different slices of the input data. This is in contrast to + _model parallelism_ where we divide up a single copy of a model + across multiple devices. + Note: for now we only support data parallelism at this time, but + hope to add support for model parallelism in the future. + * A _tower_ is one copy of the model, running on one slice of the + input data. + * _Synchronous_, or more commonly _sync_, training is when the + updates from each tower are aggregated together before updating + the model variables. This is in contrast to _asynchronous_, or + _async_ training where each tower updates the model variables + independently. + * Furthermore you might run your computation on multiple devices + on one machine (or "host"), or on multiple machines/hosts. + If you are running on multiple machines, you might have a + single master host that drives computation across all of them, + or you might have multiple clients driving the computation + asynchronously. + + To distribute an algorithm, we might use some of these ingredients: + + * Parameter servers: These are hosts that hold a single copy of + parameters/variables. All towers that want to operate on a variable + retrieve it at the beginning of a step and send an update to be + applied at the end of the step. Can support either sync or async + training. + * Mirrored variables: These are variables that are copied to multiple + devices, where we keep the copies in sync by applying the same + updates to every copy. Normally would only be used with sync training. + * Reductions and Allreduce: A _reduction_ is some method of + aggregating multiple values into one value, like "sum" or + "mean". If doing sync training, we will perform a reduction on the + gradients to a parameter from each tower before applying the + update. Allreduce is an algorithm for performing a reduction on + values from multiple devices and making the result available on + all of those devices. + * TODO(josh11b): Future: partitioned variables + + We have then a few approaches we want to support: + * Code written (as if) with no knowledge of class `DistributionStrategy`. + This code should work as before, even if some of the layers, etc. + used by that code are written to be distribution-aware. This is done + by having a default `DistributionStrategy` that gives ordinary behavior, + and by default being in a single tower context. + * Ordinary model code that you want to run using a specific + `DistributionStrategy`. This can be as simple as: + + ``` + with my_distribution.scope(): + iterator = my_distribution.distribute_dataset(dataset) + # TODO(josh11b): iterator = dataset.make_one_shot_iterator() + tower_train_ops = my_distribution.call_for_each_tower( + tower_fn, iterator.get_next()) + train_op = tf.group(my_distribution.unwrap(tower_train_ops)) + ``` + + This takes an ordinary `dataset` and `tower_fn` and runs it + distributed using a particular `DistributionStrategy` in + `my_distribution`. Any variables created in `tower_fn` are created + using `my_distribution`'s policy, and library functions called by + `tower_fn` can use the `get_tower_context()` API to get enhanced + behavior in this case. + * If you want to write a distributed algorithm, you may use any of + the `DistributionStrategy` APIs inside a + `with my_distribution.scope():` block of code. + + Lower-level concepts: + + * Wrapped values: In order to represent values parallel across devices + (either towers or the devices associated with a particular value), we + wrap them in a "PerDevice" or "Mirrored" object that contains a map + from device to values. "PerDevice" is used when the value may be + different across devices, and "Mirrored" when the value are the same. + * Unwrapping and merging: Consider calling a function `fn` on + multiple devices, like `call_for_each_tower(fn, w)` with an + argument `w that is a wrapped value. This means `w` will have a + map taking tower device `d0` to `w0`, tower device `d1` to `w1`, + etc. `call_for_each_tower()` unwraps `w` before calling `fn`, so + it calls `fn(w0)` on `d0`, `fn(w1)` on `d1`, etc. It then merges + the return values from `fn()`, which can possibly result in + wrapped values. For example, let's say `fn()` returns a tuple with + three components: (x, a, v0) from tower 0, (x, b, v1) on tower 1, + etc. If the first component is the same object `x` from every + tower, then the first component of the merged result will also be + `x`. If the second component is different (`a`, `b`, ...) from + each tower, then the merged value will have a wrapped map from + tower device to the different values. If the third component is + the members of a mirrored variable (`v` maps `d0` to `v0, `d1` to + `v1`, etc.), then the merged result will be that mirrored variable + (`v`). + * Tower context vs. Cross-tower context: _tower context_ is when we + are in some function that is being called once for each tower. + Otherwise we are in cross-tower context, which is useful for + calling `DistributionStrategy` methods which operate across the + towers (like `reduce()`). By default you start in a tower context + (the default "single tower context") and then some methods can + switch you back and forth, as described below. + * Worker devices vs. parameter devices: Most tower computations will + happen on worker devices. Since we don't yet support model + parallelism, there will be one worker device per tower. When using + parameter servers (see above), the set of devices holding + variables may be different, otherwise the parameter devices might + match the worker devices. + * Non-slot devices are some subset of the parameter devices where we + put all the non-slot variables. We need to ensure that all + non-slot variables are allocated on the same device, or mirrored + across the same set of devices. If you have some variable you want + to colocate all the non-slot variables with, you can use + `colocate_vars_with()` to get the remaining non-slot variables on + the same device. Otherwise you can use `non_slot_devices()` to + pick a consistent set of devices to pass to both + `colocate_vars_with()` and `update_non_slot()`. + + When using a `DistributionStrategy`, we have a new type dimension + called _locality_ that says what values are compatible with which + APIs: + + * T: different value for each tower (e.g. a PerDevice-wrapped value). + * M: value is "mirrored" across towers, i.e. there are copies with the + same value on each tower (e.g. a Mirrored-wrapped value). + * V(`v`): value is "mirrored" across all the devices which have a + copy of variable `v` (also a Mirrored-wrapped value, but over + parameter devices instead of worker devices). + * N: value is "mirrored" across all the "non-slot" devices + + Rules for methods with respect to locality and single-tower vs. + cross-tower context: + + * `with d.scope()`: default single-tower context -> cross-tower context for + `d` + * `with d.colocate_vars_with(v)`: in tower/cross-tower context, variables + will be created with locality V(`v`). That is, if we write + `with d.colocate_vars_with(v1): v2 = tf.get_variable(...)`, then + `v2` will have locality V(`v1`), i.e. locality V(`v2`) will equal + V(`v1`). + * `with d.colocate_vars_with(d.non_slot_devices(...))`: in + tower/cross-tower context, variables will be created with locality N + * `v = tf.get_variable(...)`: in tower/cross-tower context, creates + a variable (which by definition will have locality V(`v`), though + will match another locality if inside a `colocate_vars_with` + scope). + * `d.distribute_dataset(dataset)`: in cross-tower context, produces an + iterator with locality T + * `d.broadcast(t)`: in cross-tower context, produces a value with locality M + * `d.broadcast(t, v)`: in cross-tower context, produces a value with + locality V(`v`) + * `d.call_for_each_tower(fn, ...)`: in cross-tower context, runs + `fn()` in a tower context (and so may call `get_tower_context()` and + use its API, including `merge_call()` to get back to cross-tower + context), once for each tower. May use values with locality T or + M, and any variable. + * `d.reduce(m, t)`: in cross-tower context, accepts t with locality T + and produces a value with locality M. + * `d.reduce(m, t, v)`: in cross-tower context, accepts t with + locality T and produces a value with locality V(`v`). + * `d.batch_reduce(m, [(t, v)]): see `d.reduce()` + * `d.update(v, fn, ...)`: in cross-tower context, runs `fn()` once + for each device `v` is copied to, all inputs should have locality + V(`v`), output will have locality V(`v`) as well. + * `d.update_non_slot(d.non_slot_devices(), fn)`: in cross-tower + context, like `d.update()` except with locality N. + * `d.fetch(t)`: Copy `t` with any locality to the client's CPU device. + + The standard pattern for updating variables is to: + + 1. Wrap your input dataset in `d.distribute_dataset()`. + 2. Define each tower `d.call_for_each_tower()` up to the point of + getting a list of gradient, variable pairs. + 3. Call `d.reduce("sum", t, v)` or `d.batch_reduce()` to sum the + gradients (with locality T) into values with locality V(`v`). + 4. Call `d.update(v)` for each variable to update its value. + + Steps 3 and 4 are done automatically by class `Optimizer` if you call + its `apply_gradients` method in a tower context. Otherwise you can + manually call its `distributed_apply` method in a cross-tower context. + + Another thing you might want to do in the middle of your tower function + is an all-reduce of some intermediate value, using `d.reduce()` or + `d.batch_reduce()` without supplying a variable as the destination. + + Layers should expect to be called in a tower context, and can use + the `get_tower_context()` function to get a `TowerContext` object. The + `TowerContext` object has a `merge_call()` method for entering + cross-tower context where you can use `reduce()` (or + `batch_reduce()`) and then optionally `update()` to update state. + + You may use this API whether or not a `DistributionStrategy` is + being used, since there is a default implementation of + `TowerContext` and `DistributionStrategy`. Or you can use the + `get_tower_context().is_single_tower` property to run different code + in the distributed vs. single tower cases. + """ + + # TODO(josh11b): Raise an exception if variable paritioning requested before + # we add support. + # TODO(josh11b): Also `parameter_device_index` property? + # TODO(josh11b): `map()` + # TODO(josh11b): ClusterSpec/ClusterResolver + # TODO(josh11b): Partitioned computations, state; sharding + # TODO(josh11b): Model parallelism: "towers" with multiple devices; shuffling + # TODO(josh11b): Tower-local variables + # TODO(josh11b): List of towers with their worker and parameter devices + # (where the parameter devices may overlap in the ps case). + + def scope(self): + """Returns a context manager selecting this DistributionStrategy as current. + + Inside a `with distribution_strategy.scope():` code block, this thread + will use a variable creator set by `distribution_strategy`, and will + enter its "cross-tower context". + + Returns: + A context manager. + """ + if has_distribution_strategy(): + _require_cross_tower_context(self) + return _SameScopeAgainContext(self) + + def creator_with_resource_vars(*args, **kwargs): + _require_distribution_strategy_scope(self) + kwargs["use_resource"] = True + return self._create_variable(*args, **kwargs) + + return _CurrentDistributionContext( + self, variable_scope.variable_creator_scope(creator_with_resource_vars)) + + def _create_variable(self, next_creator, *args, **kwargs): + # Note: should support "colocate_with" argument. + raise NotImplementedError("must be implemented in descendants") + + def colocate_vars_with(self, colocate_with_variable): + """Controls which devices variables will be created on. + + Note this may only be used inside `self.scope()`. + + Example usage: + + ``` + with distribution_strategy.scope(): + var1 = tf.get_variable(...) + with distribution_strategy.colocate_vars_with(v1): + # var2 and var3 will be created on the same device(s) as var1 + var2 = tf.get_variable(...) + var3 = tf.get_variable(...) + + def fn(v1, v2, v3): + # operates on v1 from var1, v2 from var2, and v3 from var3 + + # `fn` runs on every device `v1` is on, `v2` and `v3` will be there too. + distribution_strategy.update(v1, fn, v2, v3) + ``` + + Args: + colocate_with_variable: A created in `self.scope()`. Variables created + while in the returned context manager will be on the same set of + devices as `colocate_with_variable`. + + Returns: + A context manager. + """ + def create_colocated_variable(next_creator, *args, **kwargs): + _require_distribution_strategy_scope(self) + kwargs["use_resource"] = True + kwargs["colocate_with"] = colocate_with_variable + return next_creator(*args, **kwargs) + + _require_distribution_strategy_scope(self) + return variable_scope.variable_creator_scope(create_colocated_variable) + + # TODO(josh11b): Currently this returns an iterator, but should return + # something implementing (a subset of) the Dataset API. + def distribute_dataset(self, dataset): + """Return an iterator into `dataset` split across all towers. + + Suitable for providing input to for `call_for_each_tower()`, as in: + + ``` + with distribution_strategy.scope(): + iterator = distribution_strategy.distribute_dataset(dataset) + tower_results = distribution_strategy.call_for_each_tower( + tower_fn, iterator.get_next()) + ``` + + Args: + dataset: A `tf.data.Dataset`. + + Returns: + A Dataset iterator that will produce separate splits for each tower. + """ + raise NotImplementedError("must be implemented in descendants") + + def broadcast(self, tensor, destinations=None): + """Mirror a tensor on one device to all worker devices. + + Args: + tensor: A Tensor value to broadcast. + destinations: An optional mirrored variable, device string, or + list of device strings, specifying the destination devices + to copy `tensor` to. Defaults to `self.worker_devices`. + + Returns: + A value mirrored to `destinations` devices. + """ + # TODO(josh11b): More docstring + _require_cross_tower_context(self) + return self._broadcast(tensor, destinations) + + def _broadcast(self, tensor, destinations): + raise NotImplementedError("must be implemented in descendants") + + def call_for_each_tower(self, fn, *args, **kwargs): + """Run `fn` once per tower. + + `fn` may call `tf.get_tower_context()` to access methods such as + `tower_id()` and `merge_call()`. + + `merge_call()` is used to communicate betwen the towers and + re-enter the cross-tower context. All towers pause their execution + having encountered a `merge_call()` call. After that the + `merge_fn`-function is executed. Its results are then unwrapped and + given back to each tower call. After that execution resumes until + `fn` is complete or encounters another `merge_call()`. Example: + + ```python + # Called once in "cross-tower" context. + def merge_fn(distribution, three_plus_tower_id): + # sum the values across towers + return sum(distribution.unwrap(three_plus_tower_id)) + + # Called once per tower in `distribution`, in a "tower" context. + def fn(three): + tower_ctx = tf.get_tower_context() + v = three + tower_ctx.tower_id + # Computes the sum of the `v` values across all towers. + s = tower_ctx.merge_call(merge_fn, v) + return s + v + + with distribution.scope(): + # in "cross-tower" context + ... + merged_results = distribution.call_for_each_tower(fn, 3) + # merged_results has the values from every tower execution of `fn`. + print(distribution.unwrap(merged_results)) # Prints a list + ``` + + Args: + fn: function to run (will be run once per tower). + *args: positional arguments for `fn` + **kwargs: keyword arguments for `fn`. + `"run_concurrently"`: Boolean indicating whether executions of `fn` + can be run concurrently (under eager execution only), defaults to + `True`. + + Returns: + Merged return value of `fn` across all towers. + """ + _require_cross_tower_context(self) + return self._call_for_each_tower(fn, *args, **kwargs) + + def _call_for_each_tower(self, fn, *args, **kwargs): + raise NotImplementedError("must be implemented in descendants") + + def reduce(self, method_string, value, destinations=None): + """Combine (via e.g. sum or mean) values across towers. + + Args: + method_string: A string indicating how to combine values, either + "sum" or "mean". + value: A per-device value with one value per tower. + destinations: An optional mirrored variable, a device string, + list of device strings. The return value will be copied to all + destination devices (or all the devices where the mirrored + variable resides). If `None` or unspecified, the destinations + will match the devices `value` resides on. + + Returns: + A value mirrored to `destinations`. + """ + # TODO(josh11b): More docstring + # TODO(josh11b): Return an unwrapped value if colocate_with is a + # single device. + _require_cross_tower_context(self) + return self._reduce(method_string, value, destinations) + + def _reduce(self, method_string, value, destinations): + raise NotImplementedError("must be implemented in descendants") + + def batch_reduce(self, method_string, value_destination_pairs): + """Combine multiple `reduce` calls into one for faster execution. + + Args: + method_string: A string indicating how to combine values, either + "sum" or "mean". + value_destination_pairs: A sequence of (value, destinations) + pairs. See `reduce()` for a description. + + Returns: + A list of mirrored values, one per pair in `value_destination_pairs`. + """ + # TODO(josh11b): More docstring + _require_cross_tower_context(self) + assert method_string in ("sum", "mean") + return self._batch_reduce(method_string, value_destination_pairs) + + def _batch_reduce(self, method_string, value_destination_pairs): + return [self.reduce(method_string, t, destinations=v) + for t, v in value_destination_pairs] + + def update(self, var, fn, *args, **kwargs): + """Run `fn` to update `var` using inputs mirrored to the same devices. + + If `var` is mirrored across multiple devices, then this implements + logic like: + + ``` + results = {} + for device, v in var: + with tf.device(device): + # *args and **kwargs will be unwrapped if they are mirrored. + results[device] = fn(v, *args, **kwargs) + return merged(results) + ``` + + Otherwise this returns `fn(var, *args, **kwargs)` colocated with `var`.' + + Neither *args nor **kwargs may contain per-device values. + If they contain mirrored values, they will be unwrapped before + calling `fn`. + + Args: + var: Variable, possibly mirrored to multiple devices, to operate on. + fn: Function to call. Should take the variable as the first argument. + *args: Additional positional arguments to pass to `fn()`. + **kwargs: Keyword arguments to pass to `fn()`. + + Returns: + Merged return value of `fn` across all towers. + """ + _require_cross_tower_context(self) + return self._update(var, fn, *args, **kwargs) + + def _update(self, var, fn, *args, **kwargs): + raise NotImplementedError("must be implemented in descendants") + + def update_non_slot(self, colocate_with, fn, *args, **kwargs): + """Runs `fn(*args, **kwargs)` on `colocate_with` devices. + + Args: + colocate_with: The return value of `non_slot_devices()`. + fn: Function to execute. + *args: Positional arguments to pass to `fn()`. + **kwargs: Keyword arguments to pass to `fn()`. + + Returns: + Return value of `fn`, possibly merged across devices. + """ + _require_cross_tower_context(self) + return self._update_non_slot(colocate_with, fn, *args, **kwargs) + + def _update_non_slot(self, colocate_with, fn, *args, **kwargs): + raise NotImplementedError("must be implemented in descendants") + + def fetch(self, val, destination="/device:CPU:0", fn=lambda x: x): + """Return a copy of `val` or `fn(val)` on `destination`. + + This is useful for getting a mirrored value onto a device. It + will attempt to avoid a copy by checking if the value is already + on the destination device. + + Args: + val: Value (which may be mirrored) to copy. + destination: A device string to copy the value to. + fn: An optional function to apply to the value on the source + device, before copying. + + Returns: + A `Tensor` on `destination`. + """ + _require_cross_tower_context(self) + return self._fetch(val, destination, fn) + + def _fetch(self, val, destination, fn): + raise NotImplementedError("must be implemented in descendants") + + def unwrap(self, value): + """Returns the list of all per-device values contained in `value`. + + Args: + value: A value returned by `call_for_each_tower()` or a variable + created in `scope()`. + + Returns: + A list of values contained in `value`. If `value` represents a single + value, this returns `[value].` + """ + _require_cross_tower_context(self) + return self._unwrap(value) + + def _unwrap(self, distributed_value): + raise NotImplementedError("must be implemented in descendants") + + def group(self, value, name=None): + """Shortcut for `tf.group(distribution.unwrap(value))`.""" + value = nest.flatten(self.unwrap(value)) + + if len(value) != 1 or name is not None: + return control_flow_ops.group(value, name=name) + # Special handling for the common case of one op. + v, = value + if isinstance(v, ops.Tensor): + v = v.op + return v + + @property + def is_single_tower(self): + """Returns whether there is a single tower or multiple. + + Returns: + A boolean. If `True`, `call_for_each_tower(fn)` will only call `fn` once. + If `False`, `call_for_each_tower(fn)` may call `fn` multiple times. + """ + raise NotImplementedError("must be implemented in descendants") + + @property + def num_towers(self): + """Returns number of towers, for purposes of averaging across towers.""" + raise NotImplementedError("must be implemented in descendants") + + @property + def worker_devices(self): + """Returns the list of devices used to run `call_for_each_tower()` calls.""" + # TODO(josh11b): More docstring + raise NotImplementedError("must be implemented in descendants") + + @property + def parameter_devices(self): + """Returns the list of devices used for variable and `update` placement.""" + # TODO(josh11b): More docstring + raise NotImplementedError("must be implemented in descendants") + + def non_slot_devices(self, var_list): + """Device(s) for non-slot variables. + + Create variables on these devices in a + `with colocate_vars_with(non_slot_devices(...)):` block. + Update those using `update_non_slot()`. + + Args: + var_list: The list of variables being optimized, needed with the + default `DistributionStrategy`. + """ + raise NotImplementedError("must be implemented in descendants") + + @property + def worker_device_index(self): + """An object mapping worker device to an id. + + This might be passed as an argument to `call_for_each_tower()`, as in: + + ``` + with distribution_strategy.scope(): + + def fn(device_id): + # device_id is an integer. `fn` is being executed on device: + # distribution_strategy.worker_devices[device_id]. + + distribution_strategy.call_for_each_tower( + fn, distribution_strategy.worker_device_index) + ``` + + Returns: + An index object, or the integer 0 if there is only a single tower. + """ + _require_cross_tower_context(self) + return self._worker_device_index() + + def _worker_device_index(self): + raise NotImplementedError("must be implemented in descendants") + + +# A note about the difference between the context managers +# `TowerContext` (defined here) and `_CurrentDistributionContext` +# (defined above) used by `DistributionStrategy.scope()`: +# +# * a TowerContext is only present during a `call_for_each_tower()` +# call (except during a `merge_run` call) and in such a scope it +# will be returned by calls to `get_tower_context()`. Implementers of new +# DistributionStrategy descendants will frequently also need to +# define a descendant of TowerContext, and are responsible for +# entering and exiting this context. +# +# * DistributionStrategy.scope() sets up a variable_creator scope that +# changes variable creation calls (e.g. to make mirrored +# variables). This is intended as an outer scope that users enter once +# around their model creation and graph definition. There is no +# anticipated need to define descendants of _CurrentDistributionContext. +# It sets the current DistributionStrategy for purposes of +# `get_distribution_strategy()` and `has_distribution_strategy()` +# and switches the thread mode to a "cross-tower context". +class TowerContext(object): + """DistributionStrategy API inside a `call_for_each_tower()` call.""" + + def __init__(self, distribution_strategy, tower_id): + self._distribution_strategy = distribution_strategy + self._thread_context = _InTowerThreadMode(self) + self._tower_id = tower_id + + def __enter__(self): + _push_per_thread_mode(self._thread_context) + + def __exit__(self, exception_type, exception_value, traceback): + _pop_per_thread_mode() + + def merge_call(self, merge_fn, *args, **kwargs): + """Merge args across towers and run `merge_fn` in a cross-tower context. + + This allows communication and coordination when there are multiple calls + to a model function triggered by a call to + `distribution.call_for_each_tower(model_fn, ...)`. + + See `MirroredDistribution.call_for_each_tower()` for an explanation. + + Otherwise, this is equivalent to: + + ``` + distribution = get_distribution_strategy() + with cross-tower-context(distribution): + return merge_fn(distribution, *args, **kwargs) + ``` + + Args: + merge_fn: function that joins arguments from threads that are given as + PerDevice. It accepts `DistributionStrategy` object as the first + argument. + *args: positional per-thread arguments for `merge_fn` + **kwargs: keyword per-thread arguments for `merge_fn`. + + Returns: + The return value of `merge_fn`, except for `PerDevice` values which are + unpacked. + """ + require_tower_context(self) + return self._merge_call(merge_fn, *args, **kwargs) + + def _merge_call(self, merge_fn, *args, **kwargs): + """Default implementation for single tower.""" + _push_per_thread_mode( # thread-local, so not needed with multiple threads + _CrossTowerThreadMode(self._distribution_strategy)) + try: + return merge_fn(self._distribution_strategy, *args, **kwargs) + finally: + _pop_per_thread_mode() + + @property + def is_single_tower(self): + """Returns whether there is a single tower or multiple.""" + require_tower_context(self) + return self._distribution_strategy.is_single_tower + + @property + def num_towers(self): + """Returns number of towers, for purposes of averaging across towers.""" + return self._distribution_strategy.num_towers + + @property + def tower_id(self): + """Which tower is being defined, a number from 0 to `num_towers - 1`.""" + require_tower_context(self) + return self._tower_id + + @property + def distribution_strategy(self): + """The current `DistributionStrategy` object.""" + return self._distribution_strategy + + @property + def device(self): + """The device this tower is to be executed on, as a string.""" + require_tower_context(self) + return device_util.current() + + # TODO(josh11b): Implement `start_all_reduce(method, t)` that returns + # a function returning the result of reducing `t` across all + # towers. Most likely can be implemented in terms of `merge_call()` + # and `batch_reduce()`. + +# ------------------------------------------------------------------------------ + + +class _DefaultDistributionStrategy(DistributionStrategy): + """Default `DistributionStrategy` if none is explicitly selected.""" + + def scope(self): + """Context manager setting a variable creator and `self` as current.""" + if has_distribution_strategy(): + raise RuntimeError("Must not nest DistributionStrategy scopes.") + + def creator(next_creator, *args, **kwargs): + _require_distribution_strategy_scope(self) + return next_creator(*args, **kwargs) + + return _CurrentDistributionContext( + self, variable_scope.variable_creator_scope(creator)) + + def colocate_vars_with(self, colocate_with_variable): + """Does not require `self.scope`.""" + def create_colocated_variable(next_creator, *args, **kwargs): + _require_distribution_strategy_scope(self) + with ops.colocate_with(colocate_with_variable): + return next_creator(*args, **kwargs) + + _require_distribution_strategy_scope(self) + return variable_scope.variable_creator_scope(create_colocated_variable) + + def distribute_dataset(self, dataset): + # TODO(josh11b): Support for this when executing eagerly is currently only + # in contrib. + return dataset.make_one_shot_iterator() + + def _broadcast(self, tensor, destinations): + if destinations is None: + return tensor + else: + raise NotImplementedError("TODO") + + def _call_for_each_tower(self, fn, *args, **kwargs): + # We don't run `fn` in multiple threads in _DefaultDistributionStrategy. + kwargs.pop("run_concurrently", None) + with TowerContext(self, tower_id=0): + return fn(*args, **kwargs) + + def _reduce(self, method_string, value, destinations): + # TODO(josh11b): Use destinations? + del method_string, destinations + return value + + def _update(self, var, fn, *args, **kwargs): + # TODO(josh11b): Figure out what we should be passing to UpdateContext() + # once that value is used for something. + with ops.colocate_with(var), UpdateContext(var): + return fn(var, *args, **kwargs) + + def _update_non_slot(self, colocate_with, fn, *args, **kwargs): + # TODO(josh11b): Figure out what we should be passing to UpdateContext() + # once that value is used for something. + with ops.colocate_with(colocate_with), UpdateContext(colocate_with): + return fn(*args, **kwargs) + + def _fetch(self, var, destination, fn): + with ops.colocate_with(var): + var = fn(var) + with ops.device(destination): + return array_ops.identity(var) + + def _unwrap(self, distributed_value): + return [distributed_value] + + @property + def is_single_tower(self): + return True + + @property + def num_towers(self): + return 1 + + @property + def worker_devices(self): + raise RuntimeError( + "worker_devices() method unsupported by _DefaultDistributionStrategy.") + + @property + def parameter_devices(self): + raise RuntimeError("parameter_devices() method unsupported by " + "_DefaultDistributionStrategy.") + + def non_slot_devices(self, var_list): + return min(var_list, key=lambda x: x.name) + + def _worker_device_index(self): + raise RuntimeError("worker_device_index() method unsupported by " + "_DefaultDistributionStrategy.") + + +# ------------------------------------------------------------------------------ +# Singletons + +_default_distribution_strategy = _DefaultDistributionStrategy() +_default_tower_context = TowerContext( + _default_distribution_strategy, tower_id=0) +_default_tower_mode = _DefaultTowerThreadMode() diff --git a/tensorflow/python/training/distribute_test.py b/tensorflow/python/training/distribute_test.py new file mode 100644 index 0000000000..0a4f19c31f --- /dev/null +++ b/tensorflow/python/training/distribute_test.py @@ -0,0 +1,104 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test DistributionStrategy, TowerContext, and supporting APIs.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.ops import variable_scope +from tensorflow.python.platform import test +from tensorflow.python.training import distribute + + +class _TestTowerContext(distribute.TowerContext): + + def merge_call(self, fn, *args, **kwargs): + return kwargs["test_arg"] + + +class _TestStrategy(distribute.DistributionStrategy): + + def _call_for_each_tower(self, fn, *args, **kwargs): + with _TestTowerContext(self, tower_id=0): + return fn(*args, **kwargs) + + def _create_variable(self, next_creator, *args, **kwargs): + return kwargs["name"] + + +def _assert_in_default_state(t): + t.assertIs(distribute._default_tower_context, + distribute.get_tower_context()) + t.assertIs(None, distribute.get_cross_tower_context()) + t.assertIs(distribute._default_distribution_strategy, + distribute.get_distribution_strategy()) + t.assertFalse(distribute.has_distribution_strategy()) + + +class TestStrategyTest(test.TestCase): + + def testCallForEachTower(self): + _assert_in_default_state(self) + dist = _TestStrategy() + + def run_fn(): + tower_context = distribute.get_tower_context() + self.assertTrue(tower_context is not None) + self.assertIs(None, distribute.get_cross_tower_context()) + self.assertTrue(distribute.has_distribution_strategy()) + self.assertIs(dist, distribute.get_distribution_strategy()) + self.assertEqual("foo", tower_context.merge_call(None, test_arg="foo")) + self.assertEqual("bar", variable_scope.variable(1.0, name="bar")) + + with self.assertRaises(RuntimeError): + dist.call_for_each_tower(run_fn) + with dist.scope(): + dist.call_for_each_tower(run_fn) + _assert_in_default_state(self) + + def testScope(self): + _assert_in_default_state(self) + dist = _TestStrategy() + with dist.scope(): + self.assertIs(None, distribute.get_tower_context()) + self.assertIs(dist, distribute.get_cross_tower_context()) + self.assertTrue(distribute.has_distribution_strategy()) + self.assertIs(dist, distribute.get_distribution_strategy()) + self.assertEqual("baz", variable_scope.variable(1.0, name="baz")) + _assert_in_default_state(self) + + +class DefaultDistributionStrategyTest(test.TestCase): + + def testMergeCall(self): + _assert_in_default_state(self) + + def merge_fn(dist, s): + self.assertIs(distribute._default_distribution_strategy, dist) + self.assertIs(None, distribute.get_tower_context()) + self.assertIs(dist, distribute.get_cross_tower_context()) + self.assertIs(dist, distribute.get_distribution_strategy()) + self.assertFalse(distribute.has_distribution_strategy()) + return "foo_" + s + + tower_ctx = distribute.get_tower_context() + self.assertIs(distribute._default_tower_context, tower_ctx) + self.assertEqual("foo_bar", tower_ctx.merge_call(merge_fn, "bar")) + _assert_in_default_state(self) + + +if __name__ == "__main__": + test.main() -- GitLab From b16ec315e7e9d41645634398da202629c3baa5af Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Mon, 26 Mar 2018 22:30:15 -0700 Subject: [PATCH 254/906] Use is_resource_variable() in train.assert_gloabl_step. PiperOrigin-RevId: 190573872 --- tensorflow/python/training/training_util.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tensorflow/python/training/training_util.py b/tensorflow/python/training/training_util.py index 4f1abccc96..d05e1d2c83 100644 --- a/tensorflow/python/training/training_util.py +++ b/tensorflow/python/training/training_util.py @@ -18,7 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function - from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import graph_io @@ -31,7 +30,6 @@ from tensorflow.python.ops import variables from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util.tf_export import tf_export - # Picked a long key value to minimize the chance of collision with user defined # collection keys. GLOBAL_STEP_READ_KEY = 'global_step_read_op_cache' @@ -170,8 +168,7 @@ def assert_global_step(global_step_tensor): """ if not (isinstance(global_step_tensor, variables.Variable) or isinstance(global_step_tensor, ops.Tensor) or - isinstance(global_step_tensor, - resource_variable_ops.ResourceVariable)): + resource_variable_ops.is_resource_variable(global_step_tensor)): raise TypeError( 'Existing "global_step" must be a Variable or Tensor: %s.' % global_step_tensor) -- GitLab From 307794e156bc21b2f122bf5e7d907299392023c5 Mon Sep 17 00:00:00 2001 From: Bixia Zheng Date: Mon, 26 Mar 2018 22:44:27 -0700 Subject: [PATCH 255/906] [XLA:CPU] Allow the shape partition algorithm to partition the most minor dimension. The current shape paritition algorithm does not partition the most minor dimension, because doing so causes dynamic loop bounds for the inner loop and used to prohibit LLVM vectorization. This constraint has been removed with revision 328478 and LLVM can now vectorize loops with dynamic bounds. Allow partitioning the most minor dimension is also necessary to support the parallelization of matrix-vector multiplication. Adjust shape_partition_test to reflect this change in the shape partition algorithm. PiperOrigin-RevId: 190574615 --- .../xla/service/cpu/shape_partition.cc | 5 +- .../xla/service/cpu/shape_partition_test.cc | 116 ++++++------------ .../exhaustive_f32_elementwise_op_test.cc | 4 +- 3 files changed, 43 insertions(+), 82 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/shape_partition.cc b/tensorflow/compiler/xla/service/cpu/shape_partition.cc index 61b408b8c2..42fe955f19 100644 --- a/tensorflow/compiler/xla/service/cpu/shape_partition.cc +++ b/tensorflow/compiler/xla/service/cpu/shape_partition.cc @@ -20,12 +20,13 @@ namespace cpu { std::vector ShapePartitionAssigner::Run(int64 target_partition_count) { // Gather outer-most dims where dim_size >= 'target_partition_count'. - // Note: always leave inner-dim static for vectorization/optimizations. + // This may include the inner-dim as LLVM can vectorize loops with dynamic + // bounds. std::vector outer_dims; int64 outer_dim_size = 1; // TODO(b/27458679) Consider reserving enough minor dimensions (based on // target vector register width) to enable vector instructions. - for (int i = shape_.layout().minor_to_major_size() - 1; i >= 1; --i) { + for (int i = shape_.layout().minor_to_major_size() - 1; i >= 0; --i) { const int64 dimension = shape_.layout().minor_to_major(i); outer_dims.push_back(dimension); outer_dim_size *= shape_.dimensions(dimension); diff --git a/tensorflow/compiler/xla/service/cpu/shape_partition_test.cc b/tensorflow/compiler/xla/service/cpu/shape_partition_test.cc index ee0c53fa6d..ae80a6f497 100644 --- a/tensorflow/compiler/xla/service/cpu/shape_partition_test.cc +++ b/tensorflow/compiler/xla/service/cpu/shape_partition_test.cc @@ -30,105 +30,65 @@ class ShapePartitionAssignerTest : public HloTestBase { protected: typedef std::vector Vec; - void RunR2Test(const Shape& shape, const int64 expected_max_partition_count) { + void RunR2Test(const Shape& shape, int64 max_target_partition_count, + const std::vector* expected_partitions) { ShapePartitionAssigner assigner(shape); - // Check all partitions of outer dimension. - for (int64 i = 1; i <= expected_max_partition_count; ++i) { - EXPECT_TRUE(ContainersEqual(Vec({i}), - assigner.Run(/*target_partition_count=*/i))); + // Iterate through 1..max_target_partition_count. + for (int64 i = 1; i <= max_target_partition_count; ++i) { + std::vector actual_partitions = + assigner.Run(/*target_partition_count=*/i); + EXPECT_THAT(actual_partitions, expected_partitions[i - 1]); } - // Check target_partition_count > outer dimension size. - EXPECT_TRUE(ContainersEqual( - Vec({expected_max_partition_count}), - assigner.Run( - /*target_partition_count=*/expected_max_partition_count + 1))); } }; TEST_F(ShapePartitionAssignerTest, Shape13WithLayout10) { - RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {1, 3}, {1, 0}), 1); + std::vector expected_partitions[] = {{1} /* 1 */, {1, 2} /* 2 */}; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {1, 3}, {1, 0}), 2, + expected_partitions); } TEST_F(ShapePartitionAssignerTest, Shape31WithLayout01) { - RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {3, 1}, {0, 1}), 1); + std::vector expected_partitions[] = { + {1} /* 1 */, {1, 2} /* 2 */ + }; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {3, 1}, {0, 1}), 2, + expected_partitions); } TEST_F(ShapePartitionAssignerTest, Shape53WithLayout10) { - RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3}, {1, 0}), 5); + std::vector expected_partitions[] = {{1} /* 1 */, {2} /* 2 */, + {3} /* 3 */, {4} /* 4 */, + {5} /* 5 */, {3, 2} /* 6 */}; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3}, {1, 0}), 6, + expected_partitions); } TEST_F(ShapePartitionAssignerTest, Shape53WithLayout01) { - RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3}, {0, 1}), 3); + std::vector expected_partitions[] = { + {1} /* 1 */, {2} /* 2 */, {3} /* 3 */, {2, 2} /* 4 */}; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3}, {0, 1}), 4, + expected_partitions); } TEST_F(ShapePartitionAssignerTest, Shape532WithLayout210) { - Shape shape = ShapeUtil::MakeShapeWithLayout(F32, {5, 3, 2}, {2, 1, 0}); - ShapePartitionAssigner assigner(shape); - - for (int64 i = 1; i <= 5; ++i) { - EXPECT_TRUE(ContainersEqual(Vec({i}), assigner.Run( - /*target_partition_count=*/i))); - } - - EXPECT_TRUE( - ContainersEqual(Vec({3, 2}), assigner.Run(/*target_partition_count=*/6))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 2}), assigner.Run(/*target_partition_count=*/7))); - EXPECT_TRUE( - ContainersEqual(Vec({4, 2}), assigner.Run(/*target_partition_count=*/8))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 3}), assigner.Run(/*target_partition_count=*/9))); - EXPECT_TRUE(ContainersEqual(Vec({3, 3}), - assigner.Run(/*target_partition_count=*/10))); - EXPECT_TRUE(ContainersEqual(Vec({3, 3}), - assigner.Run(/*target_partition_count=*/11))); - EXPECT_TRUE(ContainersEqual(Vec({4, 3}), - assigner.Run(/*target_partition_count=*/12))); - EXPECT_TRUE(ContainersEqual(Vec({4, 3}), - assigner.Run(/*target_partition_count=*/13))); - EXPECT_TRUE(ContainersEqual(Vec({4, 3}), - assigner.Run(/*target_partition_count=*/14))); - EXPECT_TRUE(ContainersEqual(Vec({5, 3}), - assigner.Run(/*target_partition_count=*/15))); - EXPECT_TRUE(ContainersEqual(Vec({5, 3}), - assigner.Run(/*target_partition_count=*/16))); + std::vector expected_partitions[] = { + {1} /* 1 */, {2} /* 2 */, {3} /* 3 */, {4} /* 4 */, + {5} /* 5 */, {3, 2} /* 6 */, {3, 2} /* 7 */, {4, 2} /* 8 */, + {3, 3} /* 9 */, {3, 3} /* 10 */, {3, 3} /* 11 */, {4, 3} /* 12 */, + {4, 3} /* 13 */, {4, 3} /* 14 */, {5, 3} /* 15 */, {4, 2, 2} /* 16 */}; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3, 2}, {2, 1, 0}), 16, + expected_partitions); } TEST_F(ShapePartitionAssignerTest, Shape532WithLayout201) { - Shape shape = ShapeUtil::MakeShapeWithLayout(F32, {5, 3, 2}, {2, 0, 1}); - ShapePartitionAssigner assigner(shape); - - for (int64 i = 1; i <= 3; ++i) { - EXPECT_TRUE(ContainersEqual(Vec({i}), assigner.Run( - /*target_partition_count=*/i))); - } - - EXPECT_TRUE( - ContainersEqual(Vec({2, 2}), assigner.Run(/*target_partition_count=*/4))); - EXPECT_TRUE( - ContainersEqual(Vec({2, 2}), assigner.Run(/*target_partition_count=*/5))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 2}), assigner.Run(/*target_partition_count=*/6))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 2}), assigner.Run(/*target_partition_count=*/7))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 2}), assigner.Run(/*target_partition_count=*/8))); - EXPECT_TRUE( - ContainersEqual(Vec({3, 3}), assigner.Run(/*target_partition_count=*/9))); - EXPECT_TRUE(ContainersEqual(Vec({3, 3}), - assigner.Run(/*target_partition_count=*/10))); - EXPECT_TRUE(ContainersEqual(Vec({3, 3}), - assigner.Run(/*target_partition_count=*/11))); - EXPECT_TRUE(ContainersEqual(Vec({3, 4}), - assigner.Run(/*target_partition_count=*/12))); - EXPECT_TRUE(ContainersEqual(Vec({3, 4}), - assigner.Run(/*target_partition_count=*/13))); - EXPECT_TRUE(ContainersEqual(Vec({3, 4}), - assigner.Run(/*target_partition_count=*/14))); - EXPECT_TRUE(ContainersEqual(Vec({3, 5}), - assigner.Run(/*target_partition_count=*/15))); - EXPECT_TRUE(ContainersEqual(Vec({3, 5}), - assigner.Run(/*target_partition_count=*/16))); + std::vector expected_partitions[] = { + {1} /* 1 */, {2} /* 2 */, {3} /* 3 */, {2, 2} /* 4 */, + {2, 2} /* 5 */, {3, 2} /* 6 */, {3, 2} /* 7 */, {3, 2} /* 8 */, + {3, 3} /* 9 */, {3, 3} /* 10 */, {3, 3} /* 11 */, {3, 4} /* 12 */, + {3, 4} /* 13 */, {3, 4} /* 14 */, {3, 5} /* 15 */, {3, 2, 2} /* 16 */}; + RunR2Test(ShapeUtil::MakeShapeWithLayout(F32, {5, 3, 2}, {2, 0, 1}), 16, + expected_partitions); } class ShapePartitionIteratorTest : public HloTestBase { diff --git a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc index 6fe7737de7..b28fe0c15a 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc @@ -71,8 +71,8 @@ XLA_TEST_P(ExhaustiveF32ElementwiseOpTest, LogF32) { #ifdef XLA_TEST_BACKEND_CPU // TODO(b/73141998): The vectorized Log implementation gives results outside // our error spec in this range (these numbers are bitwise representations of - // floats expressed as a zero extended int64): - std::pair known_incorrect_range = {1, 8315654}; + // floats expressed as a zero extended int64). + std::pair known_incorrect_range = {1, 8388608}; #else std::pair known_incorrect_range = {0, 0}; #endif -- GitLab From 1c38584cb9793642928bf888be1a98698d3b8c44 Mon Sep 17 00:00:00 2001 From: Michael Case Date: Mon, 26 Mar 2018 23:34:05 -0700 Subject: [PATCH 256/906] Fix acknowledgment to say "Blade Team of Tencent" in security.md file. Team is incorrectly referred to as "TenCent Blade Team" PiperOrigin-RevId: 190577449 --- SECURITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5ca304404d..a5ce3a62ee 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -244,7 +244,7 @@ v//Fw6ZeY+HmRDFdirjD7wXtIuER4vqCryIqR6Xe9X8oJXz9L/Jhslc= ### Known vulnerabilities -| Type | Versions affected | Reported by | Additional Information | -|--------------------|:-----------------:|--------------------|-----------------------------| -| Out Of Bounds Read | <=1.4 | TenCent Blade Team | [issue report](https://github.com/tensorflow/tensorflow/issues/14959) | +| Type | Versions affected | Reported by | Additional Information | +|--------------------|:-----------------:|-----------------------|-----------------------------| +| Out Of Bounds Read | <=1.4 | Blade Team of Tencent | [issue report](https://github.com/tensorflow/tensorflow/issues/14959) | -- GitLab From c66b2ed3c23240be3d6a4a609e5b87c109fb0cea Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Tue, 27 Mar 2018 00:02:42 -0700 Subject: [PATCH 257/906] Remove broken ibiblio url I suspect ibiblio selectively mirrors or perhaps only mirrors highly popular artifacts. PiperOrigin-RevId: 190578860 --- tensorflow/workspace.bzl | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index ebb9e9412f..206a5a3d99 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -576,7 +576,6 @@ def tf_workspace(path_prefix="", tf_repo_name=""): jar_urls = [ "http://mirror.bazel.build/repo1.maven.org/maven2/com/google/testing/compile/compile-testing/0.11/compile-testing-0.11.jar", "http://repo1.maven.org/maven2/com/google/testing/compile/compile-testing/0.11/compile-testing-0.11.jar", - "http://maven.ibiblio.org/maven2/com/google/testing/compile/compile-testing/0.11/compile-testing-0.11.jar", ], licenses = ["notice"], # New BSD License testonly_ = True, -- GitLab From 7555534be3c6138cbcca138556fe4dbf4cc6b8ce Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 00:30:02 -0700 Subject: [PATCH 258/906] Handle out of range values when casting from floating point to integer in quantize. PiperOrigin-RevId: 190580805 --- .../lite/kernels/internal/quantization_util.h | 69 +++++++++- .../internal/quantization_util_test.cc | 126 ++++++++++++++++++ .../toco/graph_transformations/quantize.cc | 7 +- 3 files changed, 195 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/quantization_util.h b/tensorflow/contrib/lite/kernels/internal/quantization_util.h index f7706c7938..9a04b76e56 100644 --- a/tensorflow/contrib/lite/kernels/internal/quantization_util.h +++ b/tensorflow/contrib/lite/kernels/internal/quantization_util.h @@ -97,6 +97,71 @@ QuantizationParams ChooseQuantizationParams(double rmin, double rmax) { return quantization_params; } +// Converts a floating-point number to an integer. For all inputs x where +// static_cast(x) is legal according to the C++ standard, the result +// is identical to that cast (i.e. the result is x with its fractional part +// truncated whenever that is representable as IntOut). +// +// static_cast would cause undefined behavior for the following cases, which +// have well-defined behavior for this function: +// +// 1. If x is NaN, the result is zero. +// +// 2. If the truncated form of x is above the representable range of IntOut, +// the result is std::numeric_limits::max(). +// +// 3. If the truncated form of x is below the representable range of IntOut, +// the result is std::numeric_limits::min(). +// +// Note that cases #2 and #3 cover infinities as well as finite numbers. +// +// The range of FloatIn must include the range of IntOut, otherwise +// the results are undefined. +// TODO(sfeuz): Replace by absl::SafeCast once available. +template +IntOut SafeCast(FloatIn x) { + static_assert(!std::numeric_limits::is_integer, + "FloatIn is integer"); + static_assert(std::numeric_limits::is_integer, + "IntOut is not integer"); + static_assert(std::numeric_limits::radix == 2, "IntOut is base 2"); + + // Special case NaN, for which the logic below doesn't work. + if (std::isnan(x)) { + return 0; + } + + // Negative values all clip to zero for unsigned results. + if (!std::numeric_limits::is_signed && x < 0) { + return 0; + } + + // Handle infinities. + if (std::isinf(x)) { + return x < 0 ? std::numeric_limits::min() + : std::numeric_limits::max(); + } + + // Set exp such that x == f * 2^exp for some f with |f| in [0.5, 1.0), + // unless x is zero in which case exp == 0. Note that this implies that the + // magnitude of x is strictly less than 2^exp. + int exp = 0; + std::frexp(x, &exp); + + // Let N be the number of non-sign bits in the representation of IntOut. If + // the magnitude of x is strictly less than 2^N, the truncated version of x + // is representable as IntOut. The only representable integer for which this + // is not the case is kMin for signed types (i.e. -2^N), but that is covered + // by the fall-through below. + if (exp <= std::numeric_limits::digits) { + return x; + } + + // Handle numbers with magnitude >= 2^N. + return x < 0 ? std::numeric_limits::min() + : std::numeric_limits::max(); +} + // Decompose a double multiplier into a Q0.31 int32 representation of its // significand, and shift representation of NEGATIVE its exponent --- // this is intended as a RIGHT-shift. @@ -135,8 +200,8 @@ void PreprocessSoftmaxScaling(double beta, double input_scale, // Calculate the largest input that will result in a within-bounds intermediate // result within MultiplyByQuantizedMultiplierGreaterThanOne. In other words, // it must not overflow before we reduce the value by multiplication by the -// input multiplier. The negative radius is used as the minimum difference -// in Softmax. +// input multiplier. The negative radius is used as the minimum difference in +// Softmax. int CalculateInputRadius(int input_integer_bits, int input_left_shift); } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/internal/quantization_util_test.cc b/tensorflow/contrib/lite/kernels/internal/quantization_util_test.cc index 4ae2085c30..3e9a3c29ee 100644 --- a/tensorflow/contrib/lite/kernels/internal/quantization_util_test.cc +++ b/tensorflow/contrib/lite/kernels/internal/quantization_util_test.cc @@ -22,6 +22,132 @@ namespace { using ::testing::Pair; +template +void RunSafeCastTests() { + const IntOut imax = std::numeric_limits::max(); + EXPECT_GT(imax, 0); + const IntOut imin = std::numeric_limits::min(); + const bool s = std::numeric_limits::is_signed; + if (s) { + EXPECT_LT(imin, 0); + } else { + EXPECT_EQ(0, imin); + } + + // Some basic tests. + EXPECT_EQ(SafeCast(static_cast(0.0)), 0); + EXPECT_EQ(SafeCast(static_cast(-0.0)), 0); + EXPECT_EQ(SafeCast(static_cast(0.99)), 0); + EXPECT_EQ(SafeCast(static_cast(1.0)), 1); + EXPECT_EQ(SafeCast(static_cast(1.01)), 1); + EXPECT_EQ(SafeCast(static_cast(1.99)), 1); + EXPECT_EQ(SafeCast(static_cast(2.0)), 2); + EXPECT_EQ(SafeCast(static_cast(2.01)), 2); + EXPECT_EQ(SafeCast(static_cast(-0.99)), 0); + EXPECT_EQ(SafeCast(static_cast(-1.0)), s ? -1 : 0); + EXPECT_EQ(SafeCast(static_cast(-1.01)), s ? -1 : 0); + EXPECT_EQ(SafeCast(static_cast(-1.99)), s ? -1 : 0); + EXPECT_EQ(SafeCast(static_cast(-2.0)), s ? -2 : 0); + EXPECT_EQ(SafeCast(static_cast(-2.01)), s ? -2 : 0); + EXPECT_EQ(SafeCast(static_cast(117.9)), 117); + EXPECT_EQ(SafeCast(static_cast(118.0)), 118); + EXPECT_EQ(SafeCast(static_cast(118.1)), 118); + EXPECT_EQ(SafeCast(static_cast(-117.9)), s ? -117 : 0); + EXPECT_EQ(SafeCast(static_cast(-118.0)), s ? -118 : 0); + EXPECT_EQ(SafeCast(static_cast(-118.1)), s ? -118 : 0); + + // Some edge cases. + EXPECT_EQ(SafeCast(std::numeric_limits::max()), imax); + EXPECT_EQ(SafeCast(std::numeric_limits::lowest()), imin); + EXPECT_EQ(SafeCast(std::numeric_limits::infinity()), imax); + EXPECT_EQ(SafeCast(-std::numeric_limits::infinity()), imin); + EXPECT_EQ(SafeCast(std::numeric_limits::quiet_NaN()), 0); + + // Some larger numbers. + if (sizeof(IntOut) >= 4 && sizeof(FloatIn) > 4) { + EXPECT_EQ(SafeCast(static_cast(0x76543210)), 0x76543210); + } + + if (sizeof(FloatIn) > sizeof(IntOut)) { + // Check values near imax. + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) + 0.1)), + imax); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) + 0.99)), + imax); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) + 1.0)), + imax); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) + 1.99)), + imax); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) + 2.0)), + imax); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 0.1)), + imax - 1); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 0.99)), + imax - 1); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 1.0)), + imax - 1); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 1.01)), + imax - 2); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 1.99)), + imax - 2); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 2.0)), + imax - 2); + EXPECT_EQ(SafeCast( + static_cast(static_cast(imax) - 2.01)), + imax - 3); + } + + // Check values considerably larger in magnitude than imin and imax + EXPECT_EQ( + SafeCast(static_cast(static_cast(imax) * 2)), + imax); + EXPECT_EQ( + SafeCast(static_cast(static_cast(imax) * 20)), + imax); + EXPECT_EQ( + SafeCast(static_cast(static_cast(imax) * 100)), + imax); + EXPECT_EQ( + SafeCast(static_cast(static_cast(imin) * 2)), + imin); + EXPECT_EQ( + SafeCast(static_cast(static_cast(imin) * 20)), + imin); + EXPECT_EQ( + SafeCast(static_cast(static_cast(imin) * 100)), + imin); +} + +TEST(QuantizationUtilTest, SafeCast) { + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); + RunSafeCastTests(); +} + // Example taken from http://www.tensorflow.org/performance/quantization // // Quantized | Float diff --git a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc index ad3f05274b..9679ea0a77 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc @@ -65,8 +65,6 @@ std::unique_ptr QuantizeBuffer( static_cast&>(buffer); auto* quantized_buffer = new Buffer; quantized_buffer->data.resize(float_buffer.data.size()); - const auto qmin = static_cast(std::numeric_limits>::min()); - const auto qmax = static_cast(std::numeric_limits>::max()); for (std::size_t i = 0; i < float_buffer.data.size(); i++) { const float src_val = float_buffer.data[i]; double scaled_val; // Astonishingly, using 'float' degrades accuracy just @@ -78,9 +76,8 @@ std::unique_ptr QuantizeBuffer( } else { scaled_val = quantization_params.zero_point + inverse_scale * src_val; } - const auto rounded_val = static_cast(std::round(scaled_val)); - const auto clamped_val = std::min(qmax, std::max(qmin, rounded_val)); - quantized_buffer->data[i] = static_cast>(clamped_val); + quantized_buffer->data[i] = + tflite::SafeCast>(std::round(scaled_val)); } return std::unique_ptr(quantized_buffer); } -- GitLab From 1c055f0679ea6cdae28b3c78c3bf98cb40f00e13 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 03:23:58 -0700 Subject: [PATCH 259/906] Avoid reading the input file twice for InitializableLookupTable in combination with HashTable. Before this cl, TextFileLineIterator::total_size() was called for HashTable::DoPrepare, even though HashTable::DoPrepare ignores the size parameter. In order to have a result ready for TextFileLineIterator::total_size(), Init() called GetNumLinesInTextFile(), which read the whole file. Just to throw away the result :-/ This cl: - adds a DoLazyPrepare, that gets a functor to get the size, only if needed. - add HashTable::DoLazyPrepare which does not call this functor. - modify TextFileLineIterator::Init() to not call GetNumLinesInTextFile() anymore, when vocab_size was given as -1. - modify TextFileLineIterator::total_size() to call GetNumLinesInTextFile() lazily on the first call, if vocab_size_ was passed as -1. PiperOrigin-RevId: 190593744 --- .../kernels/initializable_lookup_table.cc | 2 +- .../core/kernels/initializable_lookup_table.h | 12 ++++++++++ tensorflow/core/kernels/lookup_table_op.h | 5 ++++ tensorflow/core/kernels/lookup_util.cc | 24 +++++++++++++------ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/tensorflow/core/kernels/initializable_lookup_table.cc b/tensorflow/core/kernels/initializable_lookup_table.cc index 9c428cdedc..06d53eba30 100644 --- a/tensorflow/core/kernels/initializable_lookup_table.cc +++ b/tensorflow/core/kernels/initializable_lookup_table.cc @@ -44,7 +44,7 @@ Status InitializableLookupTable::Initialize(InitTableIterator& iter) { return errors::FailedPrecondition("Table already initialized."); } - TF_RETURN_IF_ERROR(DoPrepare(iter.total_size())); + TF_RETURN_IF_ERROR(DoLazyPrepare([&iter]() { return iter.total_size(); })); while (iter.Valid()) { TF_RETURN_IF_ERROR(DoInsert(iter.keys(), iter.values())); iter.Next(); diff --git a/tensorflow/core/kernels/initializable_lookup_table.h b/tensorflow/core/kernels/initializable_lookup_table.h index e9eae9f863..b16c76dc7f 100644 --- a/tensorflow/core/kernels/initializable_lookup_table.h +++ b/tensorflow/core/kernels/initializable_lookup_table.h @@ -114,6 +114,7 @@ class InitializableLookupTable : public LookupInterface { virtual Status status() const = 0; // Returns the total number of elements that the iterator will produce. + // It might return -1 in case of error. virtual int64 total_size() const = 0; private: @@ -129,6 +130,17 @@ class InitializableLookupTable : public LookupInterface { // number of expected elements. virtual Status DoPrepare(size_t expected_num_elements) = 0; + // Same as DoPrepare() but derived implementations might choose to skip + // calling get_expected_num_elements if size is not needed for DoPrepare. + virtual Status DoLazyPrepare( + std::function get_expected_num_elements) { + int64 expected_num_elements = get_expected_num_elements(); + if (expected_num_elements < 0) { + return errors::FailedPrecondition("Got negative expected_num_elements."); + } + return DoPrepare(expected_num_elements); + } + // Populates the table in batches given keys and values as tensors into the // underlying data structure. virtual Status DoInsert(const Tensor& keys, const Tensor& values) = 0; diff --git a/tensorflow/core/kernels/lookup_table_op.h b/tensorflow/core/kernels/lookup_table_op.h index 5ba9b936e4..3657fd5b6a 100644 --- a/tensorflow/core/kernels/lookup_table_op.h +++ b/tensorflow/core/kernels/lookup_table_op.h @@ -191,6 +191,11 @@ class HashTable : public InitializableLookupTable { return Status::OK(); }; + Status DoLazyPrepare(std::function unused) override { + constexpr size_t kUnusedSize = 0; + return DoPrepare(kUnusedSize); + } + Status DoInsert(const Tensor& keys, const Tensor& values) override { if (!table_) { return errors::FailedPrecondition("HashTable is not prepared."); diff --git a/tensorflow/core/kernels/lookup_util.cc b/tensorflow/core/kernels/lookup_util.cc index c7ce1c3747..27031d9216 100644 --- a/tensorflow/core/kernels/lookup_util.cc +++ b/tensorflow/core/kernels/lookup_util.cc @@ -75,9 +75,6 @@ class TextFileLineIterator Status Init(const string& filename, int64 vocab_size, char delimiter, DataType key_dtype, int64 key_index, DataType value_dtype, int64 value_index, Env* env) { - if (vocab_size == -1) { - TF_RETURN_IF_ERROR(GetNumLinesInTextFile(env, filename, &vocab_size)); - } filename_ = filename; vocab_size_ = vocab_size; delimiter_ = delimiter; @@ -85,6 +82,7 @@ class TextFileLineIterator value_ = Tensor(value_dtype, TensorShape({})); key_index_ = key_index; value_index_ = value_index; + env_ = env; status_ = env->NewRandomAccessFile(filename_, &file_); if (!status_.ok()) return status_; @@ -103,15 +101,15 @@ class TextFileLineIterator string line; status_ = input_buffer_->ReadLine(&line); if (!status_.ok()) { - if (errors::IsOutOfRange(status_) && next_id_ != vocab_size_) { + if (errors::IsOutOfRange(status_) && next_id_ != total_size()) { status_ = errors::InvalidArgument("Invalid vocab_size in ", filename_, - ": expected ", vocab_size_, + ": expected ", total_size(), " but got ", next_id_); } valid_ = false; return; } - if (next_id_ >= vocab_size_) { + if (vocab_size_ != -1 && next_id_ >= vocab_size_) { LOG(WARNING) << "Truncated " << filename_ << " before its end at " << vocab_size_ << " records."; LOG(WARNING) << "next_id_ : " << next_id_; @@ -162,7 +160,18 @@ class TextFileLineIterator Status status() const override { return status_; } - int64 total_size() const override { return vocab_size_; } + int64 total_size() const override { + if (vocab_size_ == -1) { + int64 new_size; + Status status = GetNumLinesInTextFile(env_, filename_, &new_size); + if (!status.ok()) { + LOG(WARNING) << "Unable to get line count: " << status; + new_size = -1; + } + *const_cast(&vocab_size_) = new_size; + } + return vocab_size_; + } private: Tensor key_; @@ -170,6 +179,7 @@ class TextFileLineIterator bool valid_; // true if the iterator points to an existing range. int64 key_index_; int64 value_index_; + Env* env_; int64 next_id_; int64 vocab_size_; string filename_; -- GitLab From 3d9f820ff2b4c7e79f9e3239b2a09472e99448e2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 03:48:57 -0700 Subject: [PATCH 260/906] Don't flush denormals when calling Eigen::SelfAdjointEigenSolver. PiperOrigin-RevId: 190595222 --- tensorflow/BUILD | 1 + tensorflow/contrib/cmake/python_modules.txt | 1 + tensorflow/core/kernels/BUILD | 4 +-- .../core/kernels/self_adjoint_eig_op.cc | 4 +++ .../kernels/self_adjoint_eig_v2_op_impl.h | 4 +++ tensorflow/python/kernel_tests/BUILD | 1 + .../kernel_tests/self_adjoint_eig_op_test.py | 17 ++++++++++ tensorflow/python/kernel_tests/testdata/BUILD | 24 ++++++++++++++ ...lf_adjoint_eig_fail_if_denorms_flushed.txt | 32 +++++++++++++++++++ tensorflow/tools/pip_package/BUILD | 1 + 10 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tensorflow/python/kernel_tests/testdata/BUILD create mode 100644 tensorflow/python/kernel_tests/testdata/self_adjoint_eig_fail_if_denorms_flushed.txt diff --git a/tensorflow/BUILD b/tensorflow/BUILD index b073adfee9..6ab43638ba 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -654,6 +654,7 @@ filegroup( "//tensorflow/python/kernel_tests/distributions:all_files", "//tensorflow/python/kernel_tests/linalg:all_files", "//tensorflow/python/kernel_tests/random:all_files", + "//tensorflow/python/kernel_tests/testdata:all_files", "//tensorflow/python/ops/distributions:all_files", "//tensorflow/python/ops/linalg:all_files", "//tensorflow/python/ops/losses:all_files", diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index f7d3c73b2c..112b690511 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -82,6 +82,7 @@ tensorflow/python/kernel_tests tensorflow/python/kernel_tests/distributions tensorflow/python/kernel_tests/linalg tensorflow/python/kernel_tests/random +tensorflow/python/kernel_tests/testdata tensorflow/python/layers tensorflow/python/lib tensorflow/python/lib/core diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 9bb80eb892..b469c01881 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2528,13 +2528,13 @@ tf_kernel_library( tf_kernel_library( name = "self_adjoint_eig_op", prefix = "self_adjoint_eig_op", - deps = LINALG_DEPS, + deps = LINALG_DEPS + ["//tensorflow/core:lib_internal"], ) tf_kernel_library( name = "self_adjoint_eig_v2_op", prefix = "self_adjoint_eig_v2_op", - deps = LINALG_DEPS + if_cuda([ + deps = LINALG_DEPS + ["//tensorflow/core:lib_internal"] + if_cuda([ ":cast_op", ":cwise_op", ]), diff --git a/tensorflow/core/kernels/self_adjoint_eig_op.cc b/tensorflow/core/kernels/self_adjoint_eig_op.cc index bcd8877390..cea5883db7 100644 --- a/tensorflow/core/kernels/self_adjoint_eig_op.cc +++ b/tensorflow/core/kernels/self_adjoint_eig_op.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/kernels/linalg_ops_common.h" #include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/platform/denormal.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" @@ -55,6 +56,9 @@ class SelfAdjointEigOp : public LinearAlgebraOp { return; } + // This algorithm relies on denormals, so switch them back on locally. + port::ScopedDontFlushDenormal dont_flush_denormals; + Eigen::SelfAdjointEigenSolver< Eigen::Matrix> es(inputs[0]); diff --git a/tensorflow/core/kernels/self_adjoint_eig_v2_op_impl.h b/tensorflow/core/kernels/self_adjoint_eig_v2_op_impl.h index 8c0633f422..271dd2c485 100644 --- a/tensorflow/core/kernels/self_adjoint_eig_v2_op_impl.h +++ b/tensorflow/core/kernels/self_adjoint_eig_v2_op_impl.h @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/kernels/linalg_ops_common.h" #include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/platform/denormal.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" @@ -61,6 +62,9 @@ class SelfAdjointEigV2Op : public LinearAlgebraOp { return; } + // This algorithm relies on denormals, so switch them back on locally. + port::ScopedDontFlushDenormal dont_flush_denormals; + Eigen::SelfAdjointEigenSolver eig( inputs[0], compute_v_ ? Eigen::ComputeEigenvectors : Eigen::EigenvaluesOnly); diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index dbe1bd437e..228d1c2452 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -2724,6 +2724,7 @@ cuda_py_test( "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", ], + data = ["//tensorflow/python/kernel_tests/testdata:self_adjoint_eig_op_test_files"], shard_count = 20, ) diff --git a/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py b/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py index 4de5f4e4db..d2647088c5 100644 --- a/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py +++ b/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py @@ -71,6 +71,23 @@ class SelfAdjointEigTest(test.TestCase): self.assertAllEqual(val[4], val[5]) self.assertAllEqual(val[1], val[3]) + def testMatrixThatFailsWhenFlushingDenormsToZero(self): + # Test a 32x32 matrix which is known to fail if denorm floats are flushed to + # zero. + matrix = np.genfromtxt( + test.test_src_dir_path( + "python/kernel_tests/testdata/" + "self_adjoint_eig_fail_if_denorms_flushed.txt")).astype(np.float32) + self.assertEqual(matrix.shape, (32, 32)) + matrix_tensor = constant_op.constant(matrix) + with self.test_session(use_gpu=True) as sess: + (e, v) = sess.run(linalg_ops.self_adjoint_eig(matrix_tensor)) + self.assertEqual(e.size, 32) + self.assertAllClose( + np.matmul(v, v.transpose()), np.eye(32, dtype=np.float32), atol=2e-3) + self.assertAllClose(matrix, + np.matmul(np.matmul(v, np.diag(e)), v.transpose())) + def SortEigenDecomposition(e, v): if v.ndim < 2: diff --git a/tensorflow/python/kernel_tests/testdata/BUILD b/tensorflow/python/kernel_tests/testdata/BUILD new file mode 100644 index 0000000000..a4a0dfc139 --- /dev/null +++ b/tensorflow/python/kernel_tests/testdata/BUILD @@ -0,0 +1,24 @@ +# Data files for kernel tests. + +package( + default_visibility = ["//tensorflow:internal"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "self_adjoint_eig_op_test_files", + srcs = ["self_adjoint_eig_fail_if_denorms_flushed.txt"], +) + +filegroup( + name = "all_files", + srcs = glob( + ["**/*"], + exclude = [ + "**/METADATA", + "**/OWNERS", + ], + ), + visibility = ["//tensorflow:__subpackages__"], +) diff --git a/tensorflow/python/kernel_tests/testdata/self_adjoint_eig_fail_if_denorms_flushed.txt b/tensorflow/python/kernel_tests/testdata/self_adjoint_eig_fail_if_denorms_flushed.txt new file mode 100644 index 0000000000..d56a690a79 --- /dev/null +++ b/tensorflow/python/kernel_tests/testdata/self_adjoint_eig_fail_if_denorms_flushed.txt @@ -0,0 +1,32 @@ +2.60986303e-17 -9.66826148e-21 -1.68610775e-24 -9.16104778e-17 -1.1039539e-18 -1.66460338e-25 -2.12362492e-23 1.90946688e-21 -3.34190535e-22 1.2000634e-18 -7.31782583e-20 2.57851762e-20 -2.55509e-20 -9.54284927e-20 -1.04248315e-17 -5.32450516e-22 -1.81712853e-17 6.0044594e-18 3.96602716e-11 2.89077487e-25 -2.47461475e-25 1.77941757e-24 -7.30388687e-21 -3.84350041e-16 -3.88532388e-21 -4.29928618e-21 4.13551131e-16 -2.63408791e-25 -2.84830375e-21 -1.6450072e-16 -2.8585296e-21 -3.65413296e-21 +-9.66826148e-21 5.03939189e-22 9.17361108e-26 5.17304053e-20 1.99338895e-20 1.25259775e-28 -8.70441942e-26 9.91474109e-25 -5.80960164e-24 -1.19022314e-21 3.90467165e-22 -1.38179098e-22 1.79253406e-22 2.23977705e-22 1.1864143e-19 7.16291934e-24 4.10159639e-20 -2.16798529e-20 -4.95460504e-14 -2.6881406e-27 5.32861213e-27 -4.54567085e-28 1.99794328e-23 1.26854541e-17 -1.92916739e-23 8.60632417e-24 -1.04721097e-18 -7.00607669e-28 6.86771954e-23 8.65173173e-19 1.24469175e-22 6.03883081e-24 +-1.68610775e-24 9.17361108e-26 1.34889529e-26 2.65059e-22 2.39713735e-23 -2.00915344e-30 -1.135692e-27 -6.46049964e-26 -1.03607712e-26 -1.57623654e-23 -1.63805162e-24 -5.95741642e-25 3.24984759e-25 6.49561204e-24 2.28504969e-21 2.8319611e-25 3.96494845e-22 -2.1988623e-22 6.26027228e-16 1.2418479e-30 2.1016041e-30 6.22813846e-30 -1.0708067e-25 6.90778045e-21 1.86361622e-25 7.08789674e-26 -9.23628499e-21 1.65335067e-30 -1.12173032e-26 8.2257321e-22 -4.72686764e-27 -2.58501275e-26 +-9.16104778e-17 5.17304053e-20 2.65059e-22 2.69965968e-14 7.06005733e-17 1.69851446e-22 -2.75994304e-21 -6.61589523e-20 3.8682048e-20 -1.69253147e-17 -2.68580354e-18 -7.74994098e-19 -9.75466696e-19 2.13537585e-18 2.13185342e-16 6.89417478e-21 1.35805044e-16 -3.48309239e-16 1.0448622e-09 -2.17287918e-23 7.41749185e-24 -7.36683057e-23 -1.31083094e-20 1.574e-14 5.72646592e-19 -9.85673749e-21 -1.0654985e-14 2.70679318e-23 4.0943479e-20 -3.42938568e-15 8.57373804e-20 -2.18094505e-20 +-1.1039539e-18 1.99338895e-20 2.39713735e-23 7.06005733e-17 1.83801666e-17 1.09735975e-24 -5.73058223e-24 7.2227645e-22 -8.94843118e-22 -2.30558605e-19 -7.84892038e-20 -1.88692532e-20 -1.02217713e-20 2.95458834e-20 2.42873413e-17 8.89161401e-22 1.21669872e-17 -6.85317731e-18 -7.345906e-12 -3.1158751e-25 1.36359449e-24 -1.57981417e-24 3.89633371e-21 9.94580899e-16 1.45732115e-20 6.92065325e-22 -1.86114433e-16 6.00601346e-26 3.26844e-21 4.38573742e-17 1.06803444e-20 4.60203933e-22 +-1.66460338e-25 1.25259775e-28 -2.00915344e-30 1.69851446e-22 1.09735975e-24 5.75549306e-30 4.74050864e-29 -5.99239043e-28 -1.5784658e-27 -1.74631273e-25 -1.22702975e-25 -1.03371979e-26 -1.96967552e-26 -1.56446725e-26 -3.06462576e-25 -6.33857393e-28 -6.08829397e-24 -7.07478859e-24 -4.82614847e-18 -2.7324345e-31 1.23830207e-31 -7.96172e-31 -1.9034503e-27 -3.82709848e-22 -2.69257733e-26 -3.84934809e-27 -1.48572725e-22 4.14585761e-31 2.5611404e-28 -2.77402858e-24 3.10373361e-28 -5.09669241e-28 +-2.12362492e-23 -8.70441942e-26 -1.135692e-27 -2.75994304e-21 -5.73058223e-24 4.74050864e-29 6.28162e-26 -3.30076462e-25 -3.30065418e-25 -1.1370873e-23 -8.97722764e-24 -1.03190629e-24 -9.52908672e-25 -3.27285413e-24 1.36216664e-22 -8.0549564e-26 -1.94826821e-22 -3.64999226e-22 -2.92500975e-15 -3.00986528e-29 2.39712646e-29 -1.02470704e-28 -4.99034099e-25 -1.32277916e-19 -5.05595e-24 -3.04012473e-25 -1.44724215e-20 5.04614184e-30 -4.12370105e-26 4.20735765e-21 -1.02818953e-25 3.41267575e-26 +1.90946688e-21 9.91474109e-25 -6.46049964e-26 -6.61589523e-20 7.2227645e-22 -5.99239043e-28 -3.30076462e-25 1.8948059e-22 1.83367373e-23 1.06616038e-21 -2.81616502e-22 1.18347412e-22 8.3458038e-23 9.67703245e-24 -1.37445558e-20 2.11412652e-24 2.64820742e-21 8.02510339e-20 4.39926334e-13 9.58727772e-27 2.9838033e-28 1.29183353e-26 1.78626483e-22 3.03531056e-19 9.62612316e-23 1.33722715e-23 2.92905627e-18 -9.42286262e-28 3.23170971e-24 4.10885529e-19 -8.38673724e-25 -8.63732285e-25 +-3.34190535e-22 -5.80960164e-24 -1.03607712e-26 3.8682048e-20 -8.94843118e-22 -1.5784658e-27 -3.30065418e-25 1.83367373e-23 9.30693173e-23 1.48929558e-21 1.83278606e-21 1.08468362e-22 2.61703785e-22 4.42441537e-23 1.23906316e-20 2.55235433e-24 8.36323349e-20 1.2152038e-19 9.83332204e-14 5.14523933e-27 -3.28220159e-28 8.22099066e-27 3.34939233e-23 4.3309476e-19 5.82711129e-22 1.14299394e-22 3.25240717e-18 5.84184241e-28 -1.76991199e-24 5.5568966e-20 -2.80294941e-24 4.59071175e-24 +1.2000634e-18 -1.19022314e-21 -1.57623654e-23 -1.69253147e-17 -2.30558605e-19 -1.74631273e-25 -1.1370873e-23 1.06616038e-21 1.48929558e-21 2.05547703e-18 2.01471341e-20 2.65473229e-20 1.36331708e-20 -2.19777252e-20 -3.09825792e-18 -1.93365673e-22 -2.25608735e-18 7.98997246e-18 1.45582661e-11 6.29004356e-25 -1.14866332e-25 -5.51419319e-26 2.97082139e-21 -2.39052259e-16 1.48920411e-20 1.28589326e-21 4.27717466e-16 -4.44694851e-26 -1.80270052e-22 3.29932795e-18 -5.11645591e-22 5.53091711e-23 +-7.31782583e-20 3.90467165e-22 -1.63805162e-24 -2.68580354e-18 -7.84892038e-20 -1.22702975e-25 -8.97722764e-24 -2.81616502e-22 1.83278606e-21 2.01471341e-20 4.38037939e-19 -4.46678177e-21 3.48516266e-20 7.32592348e-21 1.11928135e-18 8.58541052e-23 8.80645183e-18 4.80109643e-21 -1.7163557e-11 1.92262335e-26 -2.78003951e-26 5.48322572e-25 8.95330117e-23 -1.11570766e-17 3.13666242e-20 4.47195205e-21 -1.09014604e-17 7.69340111e-26 1.64649306e-22 1.71054085e-17 1.33471053e-23 6.40747815e-22 +2.57851762e-20 -1.38179098e-22 -5.95741642e-25 -7.74994098e-19 -1.88692532e-20 -1.03371979e-26 -1.03190629e-24 1.18347412e-22 1.08468362e-22 2.65473229e-20 -4.46678177e-21 5.22731861e-21 1.06412616e-21 -8.0508039e-22 -1.68829721e-19 -2.7699538e-23 -2.15173717e-19 7.46895651e-19 1.71858101e-12 5.41956e-26 -6.15013064e-27 1.54884457e-26 2.54028029e-22 -1.50009535e-18 1.11920465e-21 1.05890428e-22 3.6487132e-17 -2.06798384e-27 -5.5143889e-23 -1.71529414e-18 -7.38099094e-23 -6.5250472e-24 +-2.55509e-20 1.79253406e-22 3.24984759e-25 -9.75466696e-19 -1.02217713e-20 -1.96967552e-26 -9.52908672e-25 8.3458038e-23 2.61703785e-22 1.36331708e-20 3.48516266e-20 1.06412616e-21 4.08927657e-20 -2.76503659e-21 -6.81059804e-20 5.13487959e-23 1.80612902e-18 5.32462054e-19 -3.89327199e-12 3.60012729e-26 -2.5575456e-26 3.14316426e-25 4.56614351e-22 -1.24545392e-17 9.14707146e-21 7.97421952e-22 2.84371096e-17 2.98359736e-26 1.33439467e-23 1.00242743e-17 -4.94476664e-23 3.28816461e-22 +-9.54284927e-20 2.23977705e-22 6.49561204e-24 2.13537585e-18 2.95458834e-20 -1.56446725e-26 -3.27285413e-24 9.67703245e-24 4.42441537e-23 -2.19777252e-20 7.32592348e-21 -8.0508039e-22 -2.76503659e-21 5.02409342e-20 1.57549297e-18 2.63027228e-22 6.11241908e-19 -2.71906856e-19 1.41003203e-12 2.66730019e-26 2.25679315e-26 1.00596535e-25 3.02875382e-22 3.85539387e-17 6.79708607e-22 1.60452617e-22 -2.08440846e-17 -5.40071056e-28 4.56236979e-23 -1.00868521e-17 1.22265047e-22 -1.81997389e-23 +-1.04248315e-17 1.1864143e-19 2.28504969e-21 2.13185342e-16 2.42873413e-17 -3.06462576e-25 1.36216664e-22 -1.37445558e-20 1.23906316e-20 -3.09825792e-18 1.11928135e-18 -1.68829721e-19 -6.81059804e-20 1.57549297e-18 2.5311263e-15 9.97996576e-20 2.26115975e-16 -3.86907114e-17 3.68487445e-12 8.23669787e-24 1.00324064e-23 3.38722042e-24 8.64234911e-21 2.46521189e-15 1.72823337e-19 9.24995431e-20 -3.16903295e-15 5.94130048e-25 1.73965082e-20 1.17371651e-15 2.26718703e-20 4.16709318e-21 +-5.32450516e-22 7.16291934e-24 2.8319611e-25 6.89417478e-21 8.89161401e-22 -6.33857393e-28 -8.0549564e-26 2.11412652e-24 2.55235433e-24 -1.93365673e-22 8.58541052e-23 -2.7699538e-23 5.13487959e-23 2.63027228e-22 9.97996576e-20 2.88326168e-23 1.35358898e-20 5.43364968e-21 4.24011412e-14 1.88486064e-27 8.93106076e-29 4.5748278e-27 2.48573168e-24 5.81165621e-19 1.96505062e-23 5.84813631e-24 -2.46866108e-20 1.912471e-29 2.0243857e-24 -2.88983463e-20 1.35761502e-24 1.40424791e-27 +-1.81712853e-17 4.10159639e-20 3.96494845e-22 1.35805044e-16 1.21669872e-17 -6.08829397e-24 -1.94826821e-22 2.64820742e-21 8.36323349e-20 -2.25608735e-18 8.80645183e-18 -2.15173717e-19 1.80612902e-18 6.11241908e-19 2.26115975e-16 1.35358898e-20 3.66013906e-15 1.35652384e-17 -1.97764849e-09 4.16586597e-24 1.28936031e-24 6.96597122e-23 2.43147439e-21 -1.25627342e-15 1.52711738e-18 2.61025243e-19 -2.00782109e-15 9.75835691e-24 4.0203e-21 1.40790259e-15 -7.8869e-21 8.51983e-20 +6.0044594e-18 -2.16798529e-20 -2.1988623e-22 -3.48309239e-16 -6.85317731e-18 -7.07478859e-24 -3.64999226e-22 8.02510339e-20 1.2152038e-19 7.98997246e-18 4.80109643e-21 7.46895651e-19 5.32462054e-19 -2.71906856e-19 -3.86907114e-17 5.43364968e-21 1.35652384e-17 1.19795414e-15 1.18472676e-09 2.74214961e-23 -7.6305178e-26 1.25969175e-23 1.68466447e-19 1.33873166e-15 1.0739288e-18 1.02533716e-19 2.73480291e-14 -1.87024011e-24 -9.73944425e-21 2.74769918e-16 -1.48632788e-20 1.69142815e-21 +3.96602716e-11 -4.95460504e-14 6.26027228e-16 1.0448622e-09 -7.345906e-12 -4.82614847e-18 -2.92500975e-15 4.39926334e-13 9.83332204e-14 1.45582661e-11 -1.7163557e-11 1.71858101e-12 -3.89327199e-12 1.41003203e-12 3.68487445e-12 4.24011412e-14 -1.97764849e-09 1.18472676e-09 0.0257282555 5.64106473e-17 5.83845666e-18 -1.72409096e-16 1.02886027e-12 1.42563525e-08 -1.57067415e-12 -4.61972799e-13 3.30651737e-08 -5.20615037e-17 -1.71347193e-14 2.87764201e-10 5.03749196e-14 -1.97989316e-13 +2.89077487e-25 -2.6881406e-27 1.2418479e-30 -2.17287918e-23 -3.1158751e-25 -2.7324345e-31 -3.00986528e-29 9.58727772e-27 5.14523933e-27 6.29004356e-25 1.92262335e-26 5.41956e-26 3.60012729e-26 2.66730019e-26 8.23669787e-24 1.88486064e-27 4.16586597e-24 2.74214961e-23 5.64106473e-17 1.2555855e-29 -1.30304595e-31 8.42884087e-31 1.75222077e-26 -2.89058862e-23 3.0225144e-26 6.67962117e-27 8.54181718e-22 -1.2385176e-32 -5.78078369e-28 3.34704626e-23 -2.00599605e-27 2.05674681e-28 +-2.47461475e-25 5.32861213e-27 2.1016041e-30 7.41749185e-24 1.36359449e-24 1.23830207e-31 2.39712646e-29 2.9838033e-28 -3.28220159e-28 -1.14866332e-25 -2.78003951e-26 -6.15013064e-27 -2.5575456e-26 2.25679315e-26 1.00324064e-23 8.93106076e-29 1.28936031e-24 -7.6305178e-26 5.83845666e-18 -1.30304595e-31 2.26490979e-30 -4.25637053e-31 1.40697e-27 5.91197152e-22 -2.08475892e-26 -5.64982671e-28 -3.97199197e-23 -5.06794406e-32 1.11993943e-27 -2.94280711e-23 2.65858181e-27 -2.23093754e-28 +1.77941757e-24 -4.54567085e-28 6.22813846e-30 -7.36683057e-23 -1.57981417e-24 -7.96172e-31 -1.02470704e-28 1.29183353e-26 8.22099066e-27 -5.51419319e-26 5.48322572e-25 1.54884457e-26 3.14316426e-25 1.00596535e-25 3.38722042e-24 4.5748278e-27 6.96597122e-23 1.25969175e-23 -1.72409096e-16 8.42884087e-31 -4.25637053e-31 1.40764294e-28 1.38735442e-26 -1.93810515e-22 1.93660175e-25 1.97417449e-26 1.62145272e-22 2.52533191e-31 -3.42833345e-28 6.34130774e-22 -2.01859e-27 6.1781768e-27 +-7.30388687e-21 1.99794328e-23 -1.0708067e-25 -1.31083094e-20 3.89633371e-21 -1.9034503e-27 -4.99034099e-25 1.78626483e-22 3.34939233e-23 2.97082139e-21 8.95330117e-23 2.54028029e-22 4.56614351e-22 3.02875382e-22 8.64234911e-21 2.48573168e-24 2.43147439e-21 1.68466447e-19 1.02886027e-12 1.75222077e-26 1.40697e-27 1.38735442e-26 1.18400807e-21 1.40670976e-18 2.40320429e-22 3.69528133e-23 4.81603371e-18 -1.49322683e-27 -2.70670724e-25 1.59463723e-19 6.40406749e-24 1.17170599e-23 +-3.84350041e-16 1.26854541e-17 6.90778045e-21 1.574e-14 9.94580899e-16 -3.82709848e-22 -1.32277916e-19 3.03531056e-19 4.3309476e-19 -2.39052259e-16 -1.11570766e-17 -1.50009535e-18 -1.24545392e-17 3.85539387e-17 2.46521189e-15 5.81165621e-19 -1.25627342e-15 1.33873166e-15 1.42563525e-08 -2.89058862e-23 5.91197152e-22 -1.93810515e-22 1.40670976e-18 4.40677789e-12 7.86017934e-19 7.73466606e-19 1.96690791e-15 -1.65941347e-22 2.63659933e-18 -3.0624544e-14 5.87194631e-18 -3.46291098e-19 +-3.88532388e-21 -1.92916739e-23 1.86361622e-25 5.72646592e-19 1.45732115e-20 -2.69257733e-26 -5.05595e-24 9.62612316e-23 5.82711129e-22 1.48920411e-20 3.13666242e-20 1.11920465e-21 9.14707146e-21 6.79708607e-22 1.72823337e-19 1.96505062e-23 1.52711738e-18 1.0739288e-18 -1.57067415e-12 3.0225144e-26 -2.08475892e-26 1.93660175e-25 2.40320429e-22 7.86017934e-19 1.80741048e-20 9.85491491e-22 5.08456938e-17 1.08072265e-26 -1.75036654e-23 4.36436952e-18 -1.77728563e-23 1.01268548e-22 +-4.29928618e-21 8.60632417e-24 7.08789674e-26 -9.85673749e-21 6.92065325e-22 -3.84934809e-27 -3.04012473e-25 1.33722715e-23 1.14299394e-22 1.28589326e-21 4.47195205e-21 1.05890428e-22 7.97421952e-22 1.60452617e-22 9.24995431e-20 5.84813631e-24 2.61025243e-19 1.02533716e-19 -4.61972799e-13 6.67962117e-27 -5.64982671e-28 1.97417449e-26 3.69528133e-23 7.73466606e-19 9.85491491e-22 3.68332283e-22 1.76753773e-18 2.6167718e-27 3.55918682e-25 1.95786374e-19 -2.60077304e-24 1.84790635e-23 +4.13551131e-16 -1.04721097e-18 -9.23628499e-21 -1.0654985e-14 -1.86114433e-16 -1.48572725e-22 -1.44724215e-20 2.92905627e-18 3.25240717e-18 4.27717466e-16 -1.09014604e-17 3.6487132e-17 2.84371096e-17 -2.08440846e-17 -3.16903295e-15 -2.46866108e-20 -2.00782109e-15 2.73480291e-14 3.30651737e-08 8.54181718e-22 -3.97199197e-23 1.62145272e-22 4.81603371e-18 1.96690791e-15 5.08456938e-17 1.76753773e-18 1.57092991e-12 -4.31425852e-23 -3.78241e-19 -1.15899865e-14 -7.61890782e-19 -1.15344546e-19 +-2.63408791e-25 -7.00607669e-28 1.65335067e-30 2.70679318e-23 6.00601346e-26 4.14585761e-31 5.04614184e-30 -9.42286262e-28 5.84184241e-28 -4.44694851e-26 7.69340111e-26 -2.06798384e-27 2.98359736e-26 -5.40071056e-28 5.94130048e-25 1.912471e-29 9.75835691e-24 -1.87024011e-24 -5.20615037e-17 -1.2385176e-32 -5.06794406e-32 2.52533191e-31 -1.49322683e-27 -1.65941347e-22 1.08072265e-26 2.6167718e-27 -4.31425852e-23 1.5576233e-30 -6.14697676e-29 -5.39097603e-24 -8.01112167e-29 1.81063126e-27 +-2.84830375e-21 6.86771954e-23 -1.12173032e-26 4.0943479e-20 3.26844e-21 2.5611404e-28 -4.12370105e-26 3.23170971e-24 -1.76991199e-24 -1.80270052e-22 1.64649306e-22 -5.5143889e-23 1.33439467e-23 4.56236979e-23 1.73965082e-20 2.0243857e-24 4.0203e-21 -9.73944425e-21 -1.71347193e-14 -5.78078369e-28 1.11993943e-27 -3.42833345e-28 -2.70670724e-25 2.63659933e-18 -1.75036654e-23 3.55918682e-25 -3.78241e-19 -6.14697676e-29 2.71732416e-23 2.4136621e-19 2.38938648e-23 1.21468477e-24 +-1.6450072e-16 8.65173173e-19 8.2257321e-22 -3.42938568e-15 4.38573742e-17 -2.77402858e-24 4.20735765e-21 4.10885529e-19 5.5568966e-20 3.29932795e-18 1.71054085e-17 -1.71529414e-18 1.00242743e-17 -1.00868521e-17 1.17371651e-15 -2.88983463e-20 1.40790259e-15 2.74769918e-16 2.87764201e-10 3.34704626e-23 -2.94280711e-23 6.34130774e-22 1.59463723e-19 -3.0624544e-14 4.36436952e-18 1.95786374e-19 -1.15899865e-14 -5.39097603e-24 2.4136621e-19 2.10373291e-13 4.84257897e-20 2.71571227e-19 +-2.8585296e-21 1.24469175e-22 -4.72686764e-27 8.57373804e-20 1.06803444e-20 3.10373361e-28 -1.02818953e-25 -8.38673724e-25 -2.80294941e-24 -5.11645591e-22 1.33471053e-23 -7.38099094e-23 -4.94476664e-23 1.22265047e-22 2.26718703e-20 1.35761502e-24 -7.8869e-21 -1.48632788e-20 5.03749196e-14 -2.00599605e-27 2.65858181e-27 -2.01859e-27 6.40406749e-24 5.87194631e-18 -1.77728563e-23 -2.60077304e-24 -7.61890782e-19 -8.01112167e-29 2.38938648e-23 4.84257897e-20 7.77486414e-23 -7.38542574e-25 +-3.65413296e-21 6.03883081e-24 -2.58501275e-26 -2.18094505e-20 4.60203933e-22 -5.09669241e-28 3.41267575e-26 -8.63732285e-25 4.59071175e-24 5.53091711e-23 6.40747815e-22 -6.5250472e-24 3.28816461e-22 -1.81997389e-23 4.16709318e-21 1.40424791e-27 8.51983e-20 1.69142815e-21 -1.97989316e-13 2.05674681e-28 -2.23093754e-28 6.1781768e-27 1.17170599e-23 -3.46291098e-19 1.01268548e-22 1.84790635e-23 -1.15344546e-19 1.81063126e-27 1.21468477e-24 2.71571227e-19 -7.38542574e-25 3.49516247e-23 diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index e01306f953..16c47f7555 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -190,6 +190,7 @@ sh_binary( "//tensorflow/python:util_example_parser_configuration", "//tensorflow/python/debug:debug_pip", "//tensorflow/python/eager:eager_pip", + "//tensorflow/python/kernel_tests/testdata:self_adjoint_eig_op_test_files", "//tensorflow/python/saved_model:saved_model", "//tensorflow/python/tools:tools_pip", "//tensorflow/python:test_ops", -- GitLab From cd98c3ac0e4ab094f00dcb2dfc1188c0c5ee08e0 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 08:01:25 -0700 Subject: [PATCH 261/906] - Added support a different strategy for cov computations in the multi-tower scenario. In this strategy we do the cov computations locally on each tower and then sum the results, as opposed to concatenating everything onto a single device. This other strategy can be enabled by setting the global variable TOWER_STRATEGY to "separate" (default value is "concat", which implements the old strategy). We might change this to use "separate" by default if this turns out to be the best default. - The code and documentation now no longer refer to the towers as computing different "mini-batches", since this was a confusing use of terminology. The best way to think about things is that the combine data over all the towers forms the mini-batch. Note however when factors process multiple towers using the "separate" strategy their batch_size variable will still refer to the amount of data in a single tower. - Fixed a bug in how the "option 1" and "option 2" RNN Fisher approximations were computed in the multi-tower scenario. - The "time-folded-into-batch" feature recently added has now changed in terms of what format it uses. Time is now the first dimension before the reshape, not the second, which is consistent with the convention used in other codebases. PiperOrigin-RevId: 190615398 --- .../python/kernel_tests/fisher_blocks_test.py | 72 ++-- .../kernel_tests/fisher_factors_test.py | 77 +++-- .../kernel_tests/layer_collection_test.py | 8 +- .../contrib/kfac/python/ops/fisher_blocks.py | 269 ++++++++++----- .../contrib/kfac/python/ops/fisher_factors.py | 317 ++++++++++++------ .../kfac/python/ops/layer_collection.py | 46 +-- tensorflow/contrib/kfac/python/ops/utils.py | 12 +- 7 files changed, 525 insertions(+), 276 deletions(-) diff --git a/tensorflow/contrib/kfac/python/kernel_tests/fisher_blocks_test.py b/tensorflow/contrib/kfac/python/kernel_tests/fisher_blocks_test.py index b70c700f09..6eda6c31e3 100644 --- a/tensorflow/contrib/kfac/python/kernel_tests/fisher_blocks_test.py +++ b/tensorflow/contrib/kfac/python/kernel_tests/fisher_blocks_test.py @@ -63,7 +63,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) self.assertAllEqual(params, block.tensors_to_compute_grads()) @@ -72,7 +72,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) self.assertAllEqual(params, block.tensors_to_compute_grads()) @@ -81,7 +81,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (params[0]**2, math_ops.sqrt(params[1])) block.instantiate_factors(grads, 0.5) @@ -91,7 +91,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (params[0]**2, math_ops.sqrt(params[1])) block.instantiate_factors((grads,), 0.5) block._factor.instantiate_cov_variables() @@ -112,7 +112,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = array_ops.constant([[1.], [2.]]) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = params**2 block.instantiate_factors((grads,), 0.5) block._factor.instantiate_cov_variables() @@ -133,7 +133,7 @@ class FullFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.FullFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (array_ops.constant([2., 3.]), array_ops.constant(4.)) damping = 0.5 block.instantiate_factors((grads,), damping) @@ -163,7 +163,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) self.assertAllEqual(params, block.tensors_to_compute_grads()) @@ -172,7 +172,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) self.assertAllEqual(params, block.tensors_to_compute_grads()) @@ -181,7 +181,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (params[0]**2, math_ops.sqrt(params[1])) block.instantiate_factors(grads, 0.5) @@ -191,7 +191,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (params[0]**2, math_ops.sqrt(params[1])) block.instantiate_factors((grads,), 0.5) block._factor.instantiate_cov_variables() @@ -210,7 +210,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = array_ops.constant([[1.], [2.]]) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = params**2 block.instantiate_factors((grads,), 0.5) block._factor.instantiate_cov_variables() @@ -228,7 +228,7 @@ class NaiveDiagonalFBTest(test.TestCase): random_seed.set_random_seed(200) params = (array_ops.constant([1., 2.]), array_ops.constant(3.)) block = fb.NaiveDiagonalFB(lc.LayerCollection(), params) - block.register_additional_minibatch(32) + block.register_additional_tower(32) grads = (params[0]**2, math_ops.sqrt(params[1])) damping = 0.5 block.instantiate_factors((grads,), damping) @@ -324,8 +324,8 @@ class FullyConnectedDiagonalFBTest(test.TestCase): self.assertAllClose(expected_result, result) - def testRegisterAdditionalMinibatch(self): - """Ensure 1 big minibatch and 2 small minibatches are equivalent.""" + def testRegisterAdditionalTower(self): + """Ensure 1 big tower and 2 small towers are equivalent.""" multiply_result_big, multiply_inverse_result_big = self.runFisherBlockOps( self.w, [self.inputs], [self.outputs], [self.output_grads]) multiply_result_small, multiply_inverse_result_small = ( @@ -376,7 +376,7 @@ class FullyConnectedDiagonalFBTest(test.TestCase): block = fb.FullyConnectedDiagonalFB( lc.LayerCollection(), has_bias=isinstance(params, (tuple, list))) for (i, o) in zip(inputs, outputs): - block.register_additional_minibatch(i, o) + block.register_additional_tower(i, o) block.instantiate_factors((output_grads,), damping=0.0) block._factor.instantiate_cov_variables() @@ -402,7 +402,7 @@ class EmbeddingKFACFBTest(test.TestCase): # Add some examples. inputs = array_ops.constant([[0, 1], [1, 2], [2, 3]]) outputs = array_ops.constant([[0.], [1.], [2.]]) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) # Instantiate factor's variables. Ensure it doesn't fail. grads = outputs**2. @@ -420,7 +420,7 @@ class EmbeddingKFACFBTest(test.TestCase): # Add some examples. inputs = array_ops.constant([[0, 1], [1, 2], [2, 3]]) outputs = array_ops.constant([[0.], [1.], [2.]]) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) # Instantiate factor's variables. Ensure it doesn't fail. grads = outputs**2. @@ -461,7 +461,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): inputs = array_ops.constant([1., 2.]) outputs = array_ops.constant([3., 4.]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection()) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self.assertAllEqual([outputs], block.tensors_to_compute_grads()) @@ -471,7 +471,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): inputs = array_ops.constant([[1., 2.], [3., 4.]]) outputs = array_ops.constant([[3., 4.], [5., 6.]]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection(), has_bias=True) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) @@ -482,7 +482,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): inputs = array_ops.constant([[1., 2.], [3., 4.]]) outputs = array_ops.constant([[3., 4.], [5., 6.]]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection(), has_bias=False) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) @@ -493,7 +493,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): inputs = array_ops.constant([[1., 2., 3.], [3., 4., 5.], [5., 6., 7.]]) outputs = array_ops.constant([[3., 4.], [5., 6.]]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection(), has_bias=False) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) @@ -525,7 +525,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): inputs = array_ops.constant([[1., 2.], [3., 4.]]) outputs = array_ops.constant([[3., 4.], [5., 6.]]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection(), has_bias=False) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) block._input_factor.instantiate_cov_variables() @@ -553,7 +553,7 @@ class FullyConnectedKFACBasicFBTest(test.TestCase): outputs = array_ops.zeros([32, output_dim]) params = array_ops.zeros([input_dim, output_dim]) block = fb.FullyConnectedKFACBasicFB(lc.LayerCollection(), has_bias=False) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 damping = 0. # This test is only valid without damping. block.instantiate_factors(((grads,),), damping) @@ -689,8 +689,8 @@ class ConvDiagonalFBTest(test.TestCase): self.assertAllClose(expected_result, result, atol=1e-3) - def testRegisterAdditionalMinibatch(self): - """Ensure 1 big minibatch and 2 small minibatches are equivalent.""" + def testRegisterAdditionalTower(self): + """Ensure 1 big tower and 2 small towers are equivalent.""" multiply_result_big, multiply_inverse_result_big = self.runFisherBlockOps( self.w, [self.inputs], [self.outputs], [self.output_grads]) multiply_result_small, multiply_inverse_result_small = ( @@ -751,7 +751,7 @@ class ConvDiagonalFBTest(test.TestCase): block = fb.ConvDiagonalFB( lc.LayerCollection(), params, strides=[1, 1, 1, 1], padding='SAME') for (i, o) in zip(inputs, outputs): - block.register_additional_minibatch(i, o) + block.register_additional_tower(i, o) block.instantiate_factors((output_grads,), damping=0.0) block._factor.instantiate_cov_variables() @@ -775,7 +775,7 @@ class DepthwiseConvKFCBasicFBTest(test.TestCase): layer_collection = lc.LayerCollection() block = fb.DepthwiseConvKFCBasicFB( layer_collection, params=params, strides=[1, 1, 1, 1], padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(([grads],), 0.5) @@ -788,7 +788,7 @@ class DepthwiseConvKFCBasicFBTest(test.TestCase): layer_collection = lc.LayerCollection() block = fb.DepthwiseConvKFCBasicFB( layer_collection, params=params, strides=[1, 1, 1, 1], padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(([grads],), 0.5) block._input_factor.instantiate_cov_variables() @@ -825,7 +825,7 @@ class ConvKFCBasicFBTest(test.TestCase): outputs = random_ops.random_normal((2, 2, 2)) block = fb.ConvKFCBasicFB( lc.LayerCollection(), params=params, padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self.assertAllEqual([outputs], block.tensors_to_compute_grads()) @@ -843,7 +843,7 @@ class ConvKFCBasicFBTest(test.TestCase): outputs = random_ops.random_normal((2, 2, 2, 2)) block = fb.ConvKFCBasicFB( lc.LayerCollection(), params=params, padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) block._input_factor.instantiate_cov_variables() @@ -874,7 +874,7 @@ class ConvKFCBasicFBTest(test.TestCase): outputs = random_ops.random_normal((2, 2, 2, 2)) block = fb.ConvKFCBasicFB( lc.LayerCollection(), params=params, padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self.assertFalse(block._has_bias) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) @@ -902,7 +902,7 @@ class ConvKFCBasicFBTest(test.TestCase): outputs = random_ops.random_normal((2, 2, 2, 2)) block = fb.ConvKFCBasicFB( lc.LayerCollection(), params=params, padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self.assertTrue(block._has_bias) grads = outputs**2 block.instantiate_factors(((grads,),), 0.5) @@ -930,7 +930,7 @@ class ConvKFCBasicFBTest(test.TestCase): outputs = array_ops.zeros((2, 2, 2, 2)) block = fb.ConvKFCBasicFB( lc.LayerCollection(), params=params, padding='SAME') - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) grads = outputs**2 damping = 0. # This test is only valid without damping. block.instantiate_factors(((grads,),), damping) @@ -964,7 +964,7 @@ class FullyConnectedSeriesFBTest(test.TestCase): inputs = array_ops.constant([1., 2.]) outputs = array_ops.constant([3., 4.]) block = fb.FullyConnectedSeriesFB(lc.LayerCollection()) - block.register_additional_minibatch([inputs], [outputs]) + block.register_additional_tower([inputs], [outputs]) self.assertAllEqual([[outputs]], block.tensors_to_compute_grads()) def testInstantiateFactorsHasBias(self): @@ -975,7 +975,7 @@ class FullyConnectedSeriesFBTest(test.TestCase): block = fb.FullyConnectedSeriesFB( lc.LayerCollection(), has_bias=True) - block.register_additional_minibatch([inputs], [outputs]) + block.register_additional_tower([inputs], [outputs]) grads = outputs**2 block.instantiate_factors((((grads,),),), 0.5) @@ -987,7 +987,7 @@ class FullyConnectedSeriesFBTest(test.TestCase): block = fb.FullyConnectedSeriesFB( lc.LayerCollection(), has_bias=False) - block.register_additional_minibatch([inputs], [outputs]) + block.register_additional_tower([inputs], [outputs]) grads = outputs**2 block.instantiate_factors((((grads,),),), 0.5) diff --git a/tensorflow/contrib/kfac/python/kernel_tests/fisher_factors_test.py b/tensorflow/contrib/kfac/python/kernel_tests/fisher_factors_test.py index e007f70939..2a3592c53f 100644 --- a/tensorflow/contrib/kfac/python/kernel_tests/fisher_factors_test.py +++ b/tensorflow/contrib/kfac/python/kernel_tests/fisher_factors_test.py @@ -85,6 +85,12 @@ class FisherFactorTestingDummy(ff.FisherFactor): def instantiate_inv_variables(self): return NotImplementedError + def _num_towers(self): + raise NotImplementedError + + def _get_data_device(self): + raise NotImplementedError + class InverseProvidingFactorTestingDummy(ff.InverseProvidingFactor): """Dummy class to test the non-abstract methods on ff.InverseProvidingFactor. @@ -116,6 +122,12 @@ class InverseProvidingFactorTestingDummy(ff.InverseProvidingFactor): def instantiate_covariance(self): pass + def _num_towers(self): + raise NotImplementedError + + def _get_data_device(self): + raise NotImplementedError + class NumericalUtilsTest(test.TestCase): @@ -430,7 +442,7 @@ class EmbeddingInputKroneckerFactorTest(test.TestCase): with tf_ops.Graph().as_default(): input_ids = array_ops.constant([[0], [1], [4]]) vocab_size = 5 - factor = ff.EmbeddingInputKroneckerFactor(input_ids, vocab_size) + factor = ff.EmbeddingInputKroneckerFactor((input_ids,), vocab_size) factor.instantiate_cov_variables() cov = factor.get_cov_var() self.assertEqual(cov.shape.as_list(), [vocab_size]) @@ -439,7 +451,7 @@ class EmbeddingInputKroneckerFactorTest(test.TestCase): with tf_ops.Graph().as_default(): input_ids = array_ops.constant([[0], [1], [4]]) vocab_size = 5 - factor = ff.EmbeddingInputKroneckerFactor(input_ids, vocab_size) + factor = ff.EmbeddingInputKroneckerFactor((input_ids,), vocab_size) factor.instantiate_cov_variables() cov_update_op = factor.make_covariance_update_op(0.0) @@ -477,8 +489,8 @@ class ConvDiagonalFactorTest(test.TestCase): ] factor = ff.ConvDiagonalFactor( - inputs, - outputs_grads, + (inputs,), + (outputs_grads,), self.kernel_shape, self.strides, self.padding, @@ -508,7 +520,8 @@ class ConvDiagonalFactorTest(test.TestCase): self.out_channels) factor = ff.ConvDiagonalFactor( - constant_op.constant(inputs), [constant_op.constant(outputs_grad)], + (constant_op.constant(inputs),), + ((constant_op.constant(outputs_grad),),), self.kernel_shape, strides=[1, 1, 1, 1], padding='VALID') @@ -537,8 +550,8 @@ class ConvDiagonalFactorTest(test.TestCase): ] factor = ff.ConvDiagonalFactor( - inputs, - outputs_grads, + (inputs,), + (outputs_grads,), self.kernel_shape, self.strides, self.padding, @@ -569,7 +582,7 @@ class FullyConnectedKroneckerFactorTest(test.TestCase): with tf_ops.Graph().as_default(): random_seed.set_random_seed(200) tensor = array_ops.ones((2, 3), dtype=dtype, name='a/b/c') - factor = ff.FullyConnectedKroneckerFactor((tensor,), has_bias=has_bias) + factor = ff.FullyConnectedKroneckerFactor(((tensor,),), has_bias=has_bias) factor.instantiate_cov_variables() cov = factor.get_cov() self.assertEqual(cov.dtype, dtype) @@ -587,7 +600,7 @@ class FullyConnectedKroneckerFactorTest(test.TestCase): with tf_ops.Graph().as_default(), self.test_session() as sess: random_seed.set_random_seed(200) tensor = array_ops.constant([[1., 2.], [3., 4.]], name='a/b/c') - factor = ff.FullyConnectedKroneckerFactor((tensor,), has_bias=True) + factor = ff.FullyConnectedKroneckerFactor(((tensor,),), has_bias=True) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -598,7 +611,7 @@ class FullyConnectedKroneckerFactorTest(test.TestCase): with tf_ops.Graph().as_default(), self.test_session() as sess: random_seed.set_random_seed(200) tensor = array_ops.constant([[1., 2.], [3., 4.]], name='a/b/c') - factor = ff.FullyConnectedKroneckerFactor((tensor,)) + factor = ff.FullyConnectedKroneckerFactor(((tensor,),)) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -629,8 +642,8 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): out_channels = 4 factor = ff.ConvInputKroneckerFactor( - inputs=random_ops.random_uniform( - (batch_size, width, width, width, in_channels), seed=0), + inputs=(random_ops.random_uniform( + (batch_size, width, width, width, in_channels), seed=0),), filter_shape=(width, width, width, in_channels, out_channels), padding='SAME', strides=(2, 2, 2), @@ -661,8 +674,8 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): out_channels = 4 factor = ff.ConvInputKroneckerFactor( - inputs=random_ops.random_uniform( - (batch_size, width, width, in_channels), seed=0), + inputs=(random_ops.random_uniform( + (batch_size, width, width, in_channels), seed=0),), filter_shape=(1, 1, in_channels, out_channels), padding='SAME', strides=(1, 1, 1, 1), @@ -691,8 +704,8 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): out_channels = 4 factor = ff.ConvInputKroneckerFactor( - inputs=random_ops.random_uniform( - (batch_size, width, width, in_channels), seed=0), + inputs=(random_ops.random_uniform( + (batch_size, width, width, in_channels), seed=0),), filter_shape=(1, 1, in_channels, out_channels), padding='SAME', strides=(1, 2, 1, 1), @@ -716,8 +729,8 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): out_channels = 4 factor = ff.ConvInputKroneckerFactor( - inputs=random_ops.random_uniform( - (batch_size, width, width, in_channels), seed=0), + inputs=(random_ops.random_uniform( + (batch_size, width, width, in_channels), seed=0),), filter_shape=(3, 3, in_channels, out_channels), padding='SAME', extract_patches_fn='extract_image_patches', @@ -739,7 +752,7 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): with tf_ops.Graph().as_default(): tensor = array_ops.ones((64, 1, 2, 3), name='a/b/c') factor = ff.ConvInputKroneckerFactor( - inputs=tensor, + inputs=(tensor,), filter_shape=(1, 2, 3, 4), padding='SAME', has_bias=False) @@ -751,7 +764,7 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): with tf_ops.Graph().as_default(): tensor = array_ops.ones((64, 1, 2, 3), name='a/b/c') factor = ff.ConvInputKroneckerFactor( - tensor, filter_shape=(1, 2, 3, 4), padding='SAME', has_bias=True) + (tensor,), filter_shape=(1, 2, 3, 4), padding='SAME', has_bias=True) factor.instantiate_cov_variables() self.assertEqual([1 * 2 * 3 + 1, 1 * 2 * 3 + 1], factor.get_cov().get_shape().as_list()) @@ -761,7 +774,7 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): dtype = dtypes.float64_ref tensor = array_ops.ones((64, 1, 2, 3), name='a/b/c', dtype=dtypes.float64) factor = ff.ConvInputKroneckerFactor( - tensor, filter_shape=(1, 2, 3, 4), padding='SAME', has_bias=True) + (tensor,), filter_shape=(1, 2, 3, 4), padding='SAME', has_bias=True) factor.instantiate_cov_variables() cov = factor.get_cov() self.assertEqual(cov.dtype, dtype) @@ -775,7 +788,7 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): np.arange(1, 1 + np.prod(input_shape)).reshape(input_shape).astype( np.float32)) factor = ff.ConvInputKroneckerFactor( - tensor, filter_shape=(1, 1, 1, 1), padding='SAME', has_bias=True) + (tensor,), filter_shape=(1, 1, 1, 1), padding='SAME', has_bias=True) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -794,7 +807,7 @@ class ConvInputKroneckerFactorTest(ConvFactorTestCase): np.arange(1, 1 + np.prod(input_shape)).reshape(input_shape).astype( np.float32)) factor = ff.ConvInputKroneckerFactor( - tensor, filter_shape=(1, 1, 1, 1), padding='SAME') + (tensor,), filter_shape=(1, 1, 1, 1), padding='SAME') factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -810,10 +823,10 @@ class ConvOutputKroneckerFactorTest(ConvFactorTestCase): width = 3 out_channels = width**3 - factor = ff.ConvOutputKroneckerFactor(outputs_grads=[ + factor = ff.ConvOutputKroneckerFactor(outputs_grads=([ random_ops.random_uniform( (batch_size, width, width, width, out_channels), seed=0) - ]) + ],)) factor.instantiate_cov_variables() with self.test_session() as sess: @@ -829,7 +842,7 @@ class ConvOutputKroneckerFactorTest(ConvFactorTestCase): with tf_ops.Graph().as_default(): random_seed.set_random_seed(200) tensor = array_ops.ones((2, 3, 4, 5), name='a/b/c') - factor = ff.ConvOutputKroneckerFactor((tensor,)) + factor = ff.ConvOutputKroneckerFactor(((tensor,),)) factor.instantiate_cov_variables() self.assertEqual([5, 5], factor.get_cov().get_shape().as_list()) @@ -838,7 +851,7 @@ class ConvOutputKroneckerFactorTest(ConvFactorTestCase): dtype = dtypes.float64_ref random_seed.set_random_seed(200) tensor = array_ops.ones((2, 3, 4, 5), dtype=dtype, name='a/b/c') - factor = ff.ConvOutputKroneckerFactor((tensor,)) + factor = ff.ConvOutputKroneckerFactor(((tensor,),)) factor.instantiate_cov_variables() cov = factor.get_cov() self.assertEqual(cov.dtype, dtype) @@ -848,7 +861,7 @@ class ConvOutputKroneckerFactorTest(ConvFactorTestCase): with tf_ops.Graph().as_default(), self.test_session() as sess: random_seed.set_random_seed(200) tensor = np.arange(1, 17).reshape(2, 2, 2, 2).astype(np.float32) - factor = ff.ConvOutputKroneckerFactor((array_ops.constant(tensor),)) + factor = ff.ConvOutputKroneckerFactor(((array_ops.constant(tensor),),)) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -862,7 +875,7 @@ class FullyConnectedMultiKFTest(test.TestCase): with tf_ops.Graph().as_default(): random_seed.set_random_seed(200) tensor = array_ops.ones((2, 3), name='a/b/c') - factor = ff.FullyConnectedMultiKF((tensor,), has_bias=False) + factor = ff.FullyConnectedMultiKF(((tensor,),), has_bias=False) factor.instantiate_cov_variables() self.assertEqual([3, 3], factor.get_cov().get_shape().as_list()) @@ -871,7 +884,7 @@ class FullyConnectedMultiKFTest(test.TestCase): dtype = dtypes.float64_ref random_seed.set_random_seed(200) tensor = array_ops.ones((2, 3), dtype=dtype, name='a/b/c') - factor = ff.FullyConnectedMultiKF((tensor,), has_bias=False) + factor = ff.FullyConnectedMultiKF(((tensor,),), has_bias=False) factor.instantiate_cov_variables() cov = factor.get_cov() self.assertEqual(cov.dtype, dtype) @@ -881,7 +894,7 @@ class FullyConnectedMultiKFTest(test.TestCase): with tf_ops.Graph().as_default(), self.test_session() as sess: random_seed.set_random_seed(200) tensor = array_ops.constant([[1., 2.], [3., 4.]], name='a/b/c') - factor = ff.FullyConnectedMultiKF((tensor,), has_bias=True) + factor = ff.FullyConnectedMultiKF(((tensor,),), has_bias=True) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) @@ -892,7 +905,7 @@ class FullyConnectedMultiKFTest(test.TestCase): with tf_ops.Graph().as_default(), self.test_session() as sess: random_seed.set_random_seed(200) tensor = array_ops.constant([[1., 2.], [3., 4.]], name='a/b/c') - factor = ff.FullyConnectedMultiKF((tensor,)) + factor = ff.FullyConnectedMultiKF(((tensor,),)) factor.instantiate_cov_variables() sess.run(tf_variables.global_variables_initializer()) diff --git a/tensorflow/contrib/kfac/python/kernel_tests/layer_collection_test.py b/tensorflow/contrib/kfac/python/kernel_tests/layer_collection_test.py index ba22099340..cb80fca370 100644 --- a/tensorflow/contrib/kfac/python/kernel_tests/layer_collection_test.py +++ b/tensorflow/contrib/kfac/python/kernel_tests/layer_collection_test.py @@ -35,7 +35,7 @@ from tensorflow.python.platform import test class MockFisherBlock(object): """A fake FisherBlock.""" - num_registered_minibatches = 2 + num_registered_towers = 2 def __init__(self, name='MockFisherBlock'): self.name = name @@ -468,13 +468,13 @@ class LayerCollectionTest(test.TestCase): b = variable_scope.get_variable('b', [3]) lc = layer_collection.LayerCollection() lc.register_fully_connected(w, inputs, outputs) - self.assertEqual(lc.fisher_blocks[w].num_registered_minibatches, 1) + self.assertEqual(lc.fisher_blocks[w].num_registered_towers, 1) with self.assertRaises(KeyError): lc.register_fully_connected((w, b), inputs, outputs, reuse=True) self.assertNotIn((w, b), lc.fisher_blocks) - self.assertEqual(lc.fisher_blocks[w].num_registered_minibatches, 1) + self.assertEqual(lc.fisher_blocks[w].num_registered_towers, 1) lc.register_fully_connected(w, inputs, outputs, reuse=True) - self.assertEqual(lc.fisher_blocks[w].num_registered_minibatches, 2) + self.assertEqual(lc.fisher_blocks[w].num_registered_towers, 2) def testMakeOrGetFactor(self): with ops.Graph().as_default(): diff --git a/tensorflow/contrib/kfac/python/ops/fisher_blocks.py b/tensorflow/contrib/kfac/python/ops/fisher_blocks.py index f517e3148f..b04bf76a88 100644 --- a/tensorflow/contrib/kfac/python/ops/fisher_blocks.py +++ b/tensorflow/contrib/kfac/python/ops/fisher_blocks.py @@ -75,37 +75,6 @@ def set_global_constants(normalize_damping_power=None, pi_type=None): PI_TYPE = pi_type -def _make_partitionedtensors_inputs(inputs): - """Constructs PartitionedTensor for inputs. - - The purpose of this method is to package up the towers/minibatch dimension - of these arrays into PartitionedTensor objects. - - Args: - inputs: a 1-D list of Tensors. Index is tower/mini-batch. - - Returns: - A PartitionedTensor. - """ - return utils.PartitionedTensor(inputs) - - -def _make_partitionedtensors_grads(grads_list): - """Constructs PartitionedTensor for grads_list. - - The purpose of this method is to package up the towers/minibatch dimension - of these arrays into PartitionedTensor objects. - - Args: - grads_list: 2-D list of Tensors. First index is for source, second - index for tower. - - Returns: - Tuple of PartitionedTensors, one per source. - """ - return tuple(utils.PartitionedTensor(grads) for grads in grads_list) - - def normalize_damping(damping, num_replications): """Normalize damping after adjusting scale by NORMALIZE_DAMPING_POWER.""" if NORMALIZE_DAMPING_POWER: @@ -191,7 +160,7 @@ class FisherBlock(object): """Abstract base class for objects modeling approximate Fisher matrix blocks. Subclasses must implement register_matpower, multiply_matpower, - instantiate_factors, tensors_to_compute_grads, and num_registered_minibatches + instantiate_factors, tensors_to_compute_grads, and num_registered_towers methods. """ @@ -266,8 +235,8 @@ class FisherBlock(object): pass @abc.abstractproperty - def num_registered_minibatches(self): - """Number of minibatches registered for this FisherBlock. + def num_registered_towers(self): + """Number of towers registered for this FisherBlock. Typically equal to the number of towers in a multi-tower setup. """ @@ -319,8 +288,8 @@ class FullFB(FisherBlock): def tensors_to_compute_grads(self): return self._params - def register_additional_minibatch(self, batch_size): - """Register an additional minibatch. + def register_additional_tower(self, batch_size): + """Register an additional tower. Args: batch_size: The batch size, used in the covariance estimator. @@ -328,7 +297,7 @@ class FullFB(FisherBlock): self._batch_sizes.append(batch_size) @property - def num_registered_minibatches(self): + def num_registered_towers(self): return len(self._batch_sizes) @property @@ -381,8 +350,8 @@ class NaiveDiagonalFB(FisherBlock): def tensors_to_compute_grads(self): return self._params - def register_additional_minibatch(self, batch_size): - """Register an additional minibatch. + def register_additional_tower(self, batch_size): + """Register an additional tower. Args: batch_size: The batch size, used in the covariance estimator. @@ -390,7 +359,7 @@ class NaiveDiagonalFB(FisherBlock): self._batch_sizes.append(batch_size) @property - def num_registered_minibatches(self): + def num_registered_towers(self): return len(self._batch_sizes) @property @@ -398,24 +367,78 @@ class NaiveDiagonalFB(FisherBlock): return math_ops.reduce_sum(self._batch_sizes) -class InputOutputMultiMinibatch(object): +class InputOutputMultiTower(object): """Mix-in class for blocks with inputs & outputs and multiple mini-batches.""" def __init__(self, *args, **kwargs): self.__inputs = [] self.__outputs = [] - super(InputOutputMultiMinibatch, self).__init__(*args, **kwargs) + super(InputOutputMultiTower, self).__init__(*args, **kwargs) + + def _process_data(self, grads_list): + """Process data into the format used by the factors. + + This function takes inputs and grads_lists data and processes it into + one of the formats expected by the FisherFactor classes (depending on + the value of the global configuration variable TOWER_STRATEGY). + + The initial format of self._inputs is expected to be a list of Tensors + over towers. Similarly grads_lists is expected to be a list over sources + of such lists. + + If TOWER_STRATEGY is "concat", 'inputs' becomes a tuple containing a single + tensor (represented as a PartitionedTensor object) equal to the + concatenation (across towers) of all of the elements of self._inputs. And + similarly grads_list is formatted into a tuple (over sources) of such + tensors (also represented as PartitionedTensors). + + If TOWER_STRATEGY is "separate", formatting of inputs and grads_list + remains unchanged from the initial format (although possibly converting + from lists into tuples). + + Args: + grads_list: grads_list in its initial format (see above). + + Returns: + inputs: self._inputs transformed into the appropriate format (see + above). + grads_list: grads_list transformed into the appropriate format (see + above). + + Raises: + ValueError: if TOWER_STRATEGY is not one of "separate" or "concat". + """ + inputs = self._inputs + # inputs is a list over towers of Tensors + # grads_list is a list of list with the first index being sources and the + # second being towers. + if fisher_factors.TOWER_STRATEGY == "concat": + # Merge towers together into a PartitionedTensor. We package it in + # a singleton tuple since the factors will expect a list over towers + inputs = (utils.PartitionedTensor(inputs),) + # Do the same for grads_list but preserve leading sources dimension + grads_list = tuple((utils.PartitionedTensor(grads),) + for grads in grads_list) + elif fisher_factors.TOWER_STRATEGY == "separate": + inputs = tuple(inputs) + grads_list = tuple(grads_list) + + else: + raise ValueError("Global config variable TOWER_STRATEGY must be one of " + "'concat' or 'separate'.") + + return inputs, grads_list def tensors_to_compute_grads(self): """Tensors to compute derivative of loss with respect to.""" - return self._outputs + return tuple(self._outputs) - def register_additional_minibatch(self, inputs, outputs): + def register_additional_tower(self, inputs, outputs): self._inputs.append(inputs) self._outputs.append(outputs) @property - def num_registered_minibatches(self): + def num_registered_towers(self): result = len(self._inputs) assert result == len(self._outputs) return result @@ -429,7 +452,7 @@ class InputOutputMultiMinibatch(object): return self.__outputs -class FullyConnectedDiagonalFB(InputOutputMultiMinibatch, FisherBlock): +class FullyConnectedDiagonalFB(InputOutputMultiTower, FisherBlock): """FisherBlock for fully-connected (dense) layers using a diagonal approx. Estimates the Fisher Information matrix's diagonal entries for a fully @@ -466,8 +489,7 @@ class FullyConnectedDiagonalFB(InputOutputMultiMinibatch, FisherBlock): super(FullyConnectedDiagonalFB, self).__init__(layer_collection) def instantiate_factors(self, grads_list, damping): - inputs = _make_partitionedtensors_inputs(self._inputs) - grads_list = _make_partitionedtensors_grads(grads_list) + inputs, grads_list = self._process_data(grads_list) self._factor = self._layer_collection.make_or_get_factor( fisher_factors.FullyConnectedDiagonalFactor, @@ -500,7 +522,7 @@ class FullyConnectedDiagonalFB(InputOutputMultiMinibatch, FisherBlock): return utils.mat2d_to_layer_params(vector, reshaped_out) -class ConvDiagonalFB(InputOutputMultiMinibatch, FisherBlock): +class ConvDiagonalFB(InputOutputMultiTower, FisherBlock): """FisherBlock for 2-D convolutional layers using a diagonal approx. Estimates the Fisher Information matrix's diagonal entries for a convolutional @@ -580,11 +602,10 @@ class ConvDiagonalFB(InputOutputMultiMinibatch, FisherBlock): super(ConvDiagonalFB, self).__init__(layer_collection) def instantiate_factors(self, grads_list, damping): - inputs = _make_partitionedtensors_inputs(self._inputs) - grads_list = _make_partitionedtensors_grads(grads_list) + inputs, grads_list = self._process_data(grads_list) # Infer number of locations upon which convolution is applied. - self._num_locations = num_conv_locations(inputs.shape.as_list(), + self._num_locations = num_conv_locations(inputs[0].shape.as_list(), self._strides) self._factor = self._layer_collection.make_or_get_factor( @@ -691,7 +712,7 @@ class KroneckerProductFB(FisherBlock): right_factor) -class EmbeddingKFACFB(InputOutputMultiMinibatch, KroneckerProductFB): +class EmbeddingKFACFB(InputOutputMultiTower, KroneckerProductFB): """K-FAC FisherBlock for embedding layers. This FisherBlock is similar to FullyConnectedKFACBasicFB, except that its @@ -723,8 +744,7 @@ class EmbeddingKFACFB(InputOutputMultiMinibatch, KroneckerProductFB): damping: 0-D Tensor or float. 'damping' * identity is approximately added to this FisherBlock's Fisher approximation. """ - inputs = _make_partitionedtensors_inputs(self._inputs) - grads_list = _make_partitionedtensors_grads(grads_list) + inputs, grads_list = self._process_data(grads_list) self._input_factor = self._layer_collection.make_or_get_factor( fisher_factors.EmbeddingInputKroneckerFactor, @@ -734,7 +754,7 @@ class EmbeddingKFACFB(InputOutputMultiMinibatch, KroneckerProductFB): self._setup_damping(damping) -class FullyConnectedKFACBasicFB(InputOutputMultiMinibatch, KroneckerProductFB): +class FullyConnectedKFACBasicFB(InputOutputMultiTower, KroneckerProductFB): """K-FAC FisherBlock for fully-connected (dense) layers. This uses the Kronecker-factorized approximation from the original @@ -764,8 +784,7 @@ class FullyConnectedKFACBasicFB(InputOutputMultiMinibatch, KroneckerProductFB): damping: 0-D Tensor or float. 'damping' * identity is approximately added to this FisherBlock's Fisher approximation. """ - inputs = _make_partitionedtensors_inputs(self._inputs) - grads_list = _make_partitionedtensors_grads(grads_list) + inputs, grads_list = self._process_data(grads_list) self._input_factor = self._layer_collection.make_or_get_factor( fisher_factors.FullyConnectedKroneckerFactor, @@ -776,7 +795,7 @@ class FullyConnectedKFACBasicFB(InputOutputMultiMinibatch, KroneckerProductFB): self._setup_damping(damping) -class ConvKFCBasicFB(InputOutputMultiMinibatch, KroneckerProductFB): +class ConvKFCBasicFB(InputOutputMultiTower, KroneckerProductFB): """FisherBlock for convolutional layers using the basic KFC approx. Estimates the Fisher Information matrix's blog for a convolutional @@ -846,8 +865,7 @@ class ConvKFCBasicFB(InputOutputMultiMinibatch, KroneckerProductFB): self._num_locations = num_conv_locations(self._inputs[0].shape.as_list(), self._strides) - inputs = _make_partitionedtensors_inputs(self._inputs) - grads_list = _make_partitionedtensors_grads(grads_list) + inputs, grads_list = self._process_data(grads_list) self._input_factor = self._layer_collection.make_or_get_factor( fisher_factors.ConvInputKroneckerFactor, @@ -1122,22 +1140,67 @@ def num_conv_locations(input_shape, strides): return spatial_input_locations // spatial_strides_divisor -class InputOutputMultiMinibatchMultiUse(InputOutputMultiMinibatch): - """Adds methods for multi-use/time-step case to InputOutputMultiMinibatch.""" +class InputOutputMultiTowerMultiUse(InputOutputMultiTower): + """Adds methods for multi-use/time-step case to InputOutputMultiTower.""" def __init__(self, num_uses=None, *args, **kwargs): self._num_uses = num_uses - super(InputOutputMultiMinibatchMultiUse, self).__init__(*args, **kwargs) + super(InputOutputMultiTowerMultiUse, self).__init__(*args, **kwargs) def _process_data(self, grads_list): - """Process temporal/multi-use data into a standard format.""" + """Process temporal/multi-use data into the format used by the factors. + + This function takes inputs and grads_lists data and processes it into + one of the formats expected by the FisherFactor classes (depending on + the value of the global configuration variable TOWER_STRATEGY). + + It accepts the data in one of two initial formats. The first possible + format is where self._inputs is a list of list of Tensors. The first index + is tower, the second is use/time-step. grads_list, meanwhile, is a list + over sources of such lists of lists. + + The second possible data format is where self._inputs is a Tensor with + uses/times-steps folded into the batch dimension. i.e. it is a Tensor + of shape [num_uses * size_batch, ...] which represents a reshape of a + Tensor of shape [num_uses, size_batch, ...]. And similarly grads_list is + a list over sources of such Tensors. + + There are two possible formats which inputs and grads_list are transformed + into. + + If TOWER_STRATEGY is "concat", 'inputs' becomes a tuple containing + a single tensor (represented as a PartitionedTensor object) with all of + the data from the towers, as well as the uses/time-steps, concatenated + together. In this tensor the leading dimension is the batch and + use/time-step dimensions folded together (with 'use' being the major of + these two, so that the tensors can be thought of as reshapes of ones of + shape [num_uses, batch_size, ...]). grads_list is similarly formatted as a + tuple over sources of such tensors. + + If TOWER_STRATEGY is "separate" the inputs are formatted into lists of + tensors over towers. Each of these tensors has a similar format to + the tensor produced by the "concat" option, except that each contains + only the data from a single tower. grads_list is similarly formatted + into a tuple over sources of such tuples. + + Args: + grads_list: grads_list in its initial format (see above). + + Returns: + inputs: self._inputs transformed into the appropriate format (see + above). + grads_list: grads_list transformed into the appropriate format (see + above). + + Raises: + ValueError: If TOWER_STRATEGY is not one of "separate" or "concat". + ValueError: If the given/initial format of self._inputs and grads_list + isn't recognized, or doesn't agree with self._num_uses. + """ inputs = self._inputs - # The first possible data format is where inputs is a list of tensors, - # one for each use/time-step. if isinstance(inputs[0], (list, tuple)): - # The first index is tower/minibatch, the second is use/time-step num_uses = len(inputs[0]) if self._num_uses is not None and self._num_uses != num_uses: raise ValueError("num_uses argument doesn't match length of inputs.") @@ -1147,15 +1210,29 @@ class InputOutputMultiMinibatchMultiUse(InputOutputMultiMinibatch): # Check that all mini-batches/towers have the same number of uses if not all(len(input_) == num_uses for input_ in inputs): raise ValueError("Length of inputs argument is inconsistent across " - "mini-batches/towers.") - # Fold uses/time-step and towers/minibatches dimensions together - inputs = nest.flatten(inputs) + "towers.") - inputs = _make_partitionedtensors_inputs(inputs) - # If inputs is not a tuple then we assume that inputs is a tensor - # with 'uses' folded into the batch dimension. (And grads_list is a list - # across sources of such Tensors.) This is the native format that the - # factor will take as arguments. + if fisher_factors.TOWER_STRATEGY == "concat": + # Reverse the tower and use/time-step indices, so that use is now first, + # and towers is second + inputs = tuple(zip(*inputs)) + + # Flatten the two dimensions + inputs = nest.flatten(inputs) + + # Merge everything together into a PartitionedTensor. We package it in + # a singleton tuple since the factors will expect a list over towers + inputs = (utils.PartitionedTensor(inputs),) + + elif fisher_factors.TOWER_STRATEGY == "separate": + # Merge together the uses/time-step dimension into PartitionedTensors, + # but keep the leading dimension (towers) intact for the factors to + # process individually. + inputs = tuple(utils.PartitionedTensor(input_) for input_ in inputs) + + else: + raise ValueError("Global config variable TOWER_STRATEGY must be one of " + "'concat' or 'separate'.") # Now we perform the analogous processing for grads_list if isinstance(grads_list[0][0], (list, tuple)): @@ -1170,10 +1247,34 @@ class InputOutputMultiMinibatchMultiUse(InputOutputMultiMinibatch): if not all(len(grad) == num_uses for grads in grads_list for grad in grads): raise ValueError("Length of outputs argument is inconsistent across " - "mini-batches/towers.") + "towers.") + + if fisher_factors.TOWER_STRATEGY == "concat": + # Reverse the tower and use/time-step indices, so that use is now first, + # and towers is second + grads_list = tuple(tuple(zip(*grads)) for grads in grads_list) + + # Flatten the two dimensions, leaving the leading dimension (source) + # intact + grads_list = tuple(nest.flatten(grads) for grads in grads_list) + + # Merge inner dimensions together into PartitionedTensors. We package + # them in a singleton tuple since the factors will expect a list over + # towers + grads_list = tuple((utils.PartitionedTensor(grads),) + for grads in grads_list) + + elif fisher_factors.TOWER_STRATEGY == "separate": + # Merge together the uses/time-step dimension into PartitionedTensors, + # but keep the leading dimension (towers) intact for the factors to + # process individually. + grads_list = tuple(tuple(utils.PartitionedTensor(grad) + for grad in grads) + for grads in grads_list) - grads_list = tuple(nest.flatten(grads) for grads in grads_list) - grads_list = _make_partitionedtensors_grads(grads_list) + else: + raise ValueError("Global config variable TOWER_STRATEGY must be one of " + "'concat' or 'separate'.") if self._num_uses is None: raise ValueError("You must supply a value for the num_uses argument if " @@ -1184,7 +1285,7 @@ class InputOutputMultiMinibatchMultiUse(InputOutputMultiMinibatch): return inputs, grads_list -class FullyConnectedMultiIndepFB(InputOutputMultiMinibatchMultiUse, +class FullyConnectedMultiIndepFB(InputOutputMultiTowerMultiUse, KroneckerProductFB): """FisherBlock for fully-connected layers that share parameters. @@ -1228,7 +1329,7 @@ class FullyConnectedMultiIndepFB(InputOutputMultiMinibatchMultiUse, return float(self._num_uses) -class ConvKFCBasicMultiIndepFB(InputOutputMultiMinibatchMultiUse, +class ConvKFCBasicMultiIndepFB(InputOutputMultiTowerMultiUse, KroneckerProductFB): """FisherBlock for 2D convolutional layers using the basic KFC approx. @@ -1309,7 +1410,7 @@ class ConvKFCBasicMultiIndepFB(InputOutputMultiMinibatchMultiUse, return self._num_locations * self._num_uses -class EmbeddingKFACMultiIndepFB(InputOutputMultiMinibatchMultiUse, +class EmbeddingKFACMultiIndepFB(InputOutputMultiTowerMultiUse, KroneckerProductFB): """K-FAC FisherBlock for embedding layers used multiple times in the graph. @@ -1320,7 +1421,7 @@ class EmbeddingKFACMultiIndepFB(InputOutputMultiMinibatchMultiUse, Does not support bias parameters. """ - def __init__(self, layer_collection, vocab_size, num_uses): + def __init__(self, layer_collection, vocab_size, num_uses=None): """Creates a EmbeddingKFACMultiIndepFB block. Args: @@ -1368,7 +1469,7 @@ class SeriesFBApproximation(enum.IntEnum): option2 = 2 -class FullyConnectedSeriesFB(InputOutputMultiMinibatchMultiUse, +class FullyConnectedSeriesFB(InputOutputMultiTowerMultiUse, KroneckerProductFB): """FisherBlock for fully-connected layers that share parameters across time. diff --git a/tensorflow/contrib/kfac/python/ops/fisher_factors.py b/tensorflow/contrib/kfac/python/ops/fisher_factors.py index f521363536..353e1c6abb 100644 --- a/tensorflow/contrib/kfac/python/ops/fisher_factors.py +++ b/tensorflow/contrib/kfac/python/ops/fisher_factors.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function import abc +import contextlib import numpy as np import six @@ -35,6 +36,8 @@ from tensorflow.python.ops import special_math_ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables from tensorflow.python.training import moving_averages +from tensorflow.python.util import nest + # Whether to initialize covariance estimators at a zero matrix (or the identity # matrix). @@ -52,16 +55,25 @@ EIGENVALUE_DECOMPOSITION_THRESHOLD = 2 # matrix powers. Must be nonnegative. EIGENVALUE_CLIPPING_THRESHOLD = 0.0 +# TOWER_STRATEGY can be one of "concat" or "separate". If "concat", the data +# passed to the factors from the blocks will be concatenated across towers +# (lazilly via PartitionedTensor objects). Otherwise a tuple of tensors over +# towers will be passed in, and the factors will iterate over this and do the +# cov computations separately for each one, averaging the results together. +TOWER_STRATEGY = "concat" + def set_global_constants(init_covariances_at_zero=None, zero_debias=None, eigenvalue_decomposition_threshold=None, - eigenvalue_clipping_threshold=None): + eigenvalue_clipping_threshold=None, + tower_strategy=None): """Sets various global constants used by the classes in this module.""" global INIT_COVARIANCES_AT_ZERO global ZERO_DEBIAS global EIGENVALUE_DECOMPOSITION_THRESHOLD global EIGENVALUE_CLIPPING_THRESHOLD + global TOWER_STRATEGY if init_covariances_at_zero is not None: INIT_COVARIANCES_AT_ZERO = init_covariances_at_zero @@ -71,6 +83,8 @@ def set_global_constants(init_covariances_at_zero=None, EIGENVALUE_DECOMPOSITION_THRESHOLD = eigenvalue_decomposition_threshold if eigenvalue_clipping_threshold is not None: EIGENVALUE_CLIPPING_THRESHOLD = eigenvalue_clipping_threshold + if tower_strategy is not None: + TOWER_STRATEGY = tower_strategy def inverse_initializer(shape, dtype, partition_info=None): # pylint: disable=unused-argument @@ -89,6 +103,15 @@ def diagonal_covariance_initializer(shape, dtype, partition_info): # pylint: di return array_ops.ones(shape, dtype) +@contextlib.contextmanager +def place_on_device(device): + if device is not None and len(device): + with tf_ops.device(device): + yield + else: + yield + + def compute_cov(tensor, tensor_right=None, normalizer=None): """Compute the empirical second moment of the rows of a 2D Tensor. @@ -255,6 +278,10 @@ class FisherFactor(object): """ pass + @abc.abstractproperty + def _num_towers(self): + pass + @abc.abstractproperty def _dtype(self): """dtype for variable backing this factor.""" @@ -277,12 +304,14 @@ class FisherFactor(object): dtype=self._dtype) @abc.abstractmethod - def _compute_new_cov(self, idx=0): + def _compute_new_cov(self, source, tower): """Computes minibatch-estimated covariance for a single source. Args: - idx: int in [0, self._num_sources). Which source to use when estimating - covariance. + source: int in [0, self._num_sources). Which source to use when computing + the cov update. + tower: int in [0, self._num_towers). Which tower to use when computing + the cov update. Returns: Tensor of same shape as self.get_cov_var(). @@ -297,15 +326,29 @@ class FisherFactor(object): Returns: An Op for updating the covariance Variable referenced by _cov. """ - new_cov_contribs = tuple(self._compute_new_cov(idx) - for idx in range(self._num_sources)) - new_cov = math_ops.add_n(new_cov_contribs) + new_cov_contribs = [] + for source in range(self._num_sources): + for tower in range(self._num_towers): + device = (self._get_data_device(tower) + if TOWER_STRATEGY == "separate" else None) + with place_on_device(device): + new_cov_contribs.append(self._compute_new_cov(source, tower)) + + new_cov = math_ops.add_n(new_cov_contribs) / float(self._num_towers) + + # I have no idea if the TPU code below is still correct since I don't know + # what it actually does. Also, this code is not present in some of the + # other versions of make_covariance_update_op. Does it matter? # Synchronize value across all TPU cores. if utils.on_tpu(): new_cov = utils.cross_replica_mean(new_cov) return moving_averages.assign_moving_average( self._cov, new_cov, ema_decay, zero_debias=ZERO_DEBIAS) + @abc.abstractmethod + def _get_data_device(self, tower): + pass + @abc.abstractmethod def instantiate_inv_variables(self): """Makes the internal "inverse" variable(s).""" @@ -596,17 +639,26 @@ class FullFactor(InverseProvidingFactor): def _num_sources(self): return len(self._params_grads) + @property + def _num_towers(self): + return 1 + @property def _dtype(self): return self._params_grads[0][0].dtype - def _compute_new_cov(self, idx=0): + def _compute_new_cov(self, source, tower): + assert tower == 0 + # This will be a very basic rank 1 estimate - params_grads_flat = utils.tensors_to_column(self._params_grads[idx]) + params_grads_flat = utils.tensors_to_column(self._params_grads[source]) return ((params_grads_flat * array_ops.transpose( params_grads_flat)) / math_ops.cast(self._batch_size, params_grads_flat.dtype)) + def _get_data_device(self, tower): + return None + class DiagonalFactor(FisherFactor): """A base class for FisherFactors that use diagonal approximations. @@ -691,15 +743,24 @@ class NaiveDiagonalFactor(DiagonalFactor): def _num_sources(self): return len(self._params_grads) + @property + def _num_towers(self): + return 1 + @property def _dtype(self): return self._params_grads[0][0].dtype - def _compute_new_cov(self, idx=0): - params_grads_flat = utils.tensors_to_column(self._params_grads[idx]) + def _compute_new_cov(self, source, tower): + assert tower == 0 + + params_grads_flat = utils.tensors_to_column(self._params_grads[source]) return (math_ops.square(params_grads_flat) / math_ops.cast( self._batch_size, params_grads_flat.dtype)) + def _get_data_device(self, tower): + return None + class EmbeddingInputKroneckerFactor(DiagonalFactor): r"""FisherFactor for input to an embedding layer. @@ -719,8 +780,8 @@ class EmbeddingInputKroneckerFactor(DiagonalFactor): """Instantiate EmbeddingInputKroneckerFactor. Args: - input_ids: Tensor of shape [batch_size, input_size] and dtype int32. - Indices into embedding matrix. + input_ids: List of Tensors of shape [batch_size, input_size] and dtype + int32. Indices into embedding matrix. List index is tower. vocab_size: int or 0-D Tensor. Maximum value for entries in 'input_ids'. dtype: dtype for covariance statistics. Must be a floating point type. Defaults to float32. @@ -743,15 +804,18 @@ class EmbeddingInputKroneckerFactor(DiagonalFactor): def _num_sources(self): return 1 + @property + def _num_towers(self): + return len(self._input_ids) + @property def _dtype(self): return self._cov_dtype - def _compute_new_cov(self, idx=0): - if idx != 0: - raise ValueError("EmbeddingInputKroneckerFactor only supports idx = 0") + def _compute_new_cov(self, source, tower): + assert source == 0 - input_ids = self._input_ids + input_ids = self._input_ids[tower] if len(input_ids.shape) > 2: raise ValueError( @@ -781,6 +845,9 @@ class EmbeddingInputKroneckerFactor(DiagonalFactor): return new_cov + def _get_data_device(self, tower): + return self._input_ids[tower].device + class FullyConnectedDiagonalFactor(DiagonalFactor): r"""FisherFactor for a diagonal approx of a fully-connected layer's Fisher. @@ -800,10 +867,11 @@ class FullyConnectedDiagonalFactor(DiagonalFactor): """Instantiate FullyConnectedDiagonalFactor. Args: - inputs: Tensor of shape [batch_size, input_size]. Inputs to this layer. + inputs: List of Tensors of shape [batch_size, input_size]. Inputs to this + layer. List index is towers. outputs_grads: List of Tensors, each of shape [batch_size, output_size], which are the gradients of the loss with respect to the layer's - outputs. One Tensor for each "source". + outputs. First index is source, second is tower. has_bias: bool. If True, append '1' to each input. """ @@ -817,47 +885,58 @@ class FullyConnectedDiagonalFactor(DiagonalFactor): @property def _var_scope(self): return "ff_diagfc_" + scope_string_from_params( - (self._inputs,) + tuple(self._outputs_grads)) + tuple(self._inputs) + tuple(nest.flatten(self._outputs_grads))) @property def _cov_shape(self): - input_size = self._inputs.shape[1] + self._has_bias - output_size = self._outputs_grads[0].shape[1] + input_size = self._inputs[0].shape[1] + self._has_bias + output_size = self._outputs_grads[0][0].shape[1] return [input_size, output_size] @property def _num_sources(self): return len(self._outputs_grads) + @property + def _num_towers(self): + return len(self._inputs) + @property def _dtype(self): - return self._outputs_grads[0].dtype + return self._outputs_grads[0][0].dtype def make_covariance_update_op(self, ema_decay): - inputs = self._inputs - if self._has_bias: - inputs = append_homog(inputs) - self._squared_inputs = math_ops.square(inputs) + self._squared_inputs = [] + for tower in range(self._num_towers): + inputs = self._inputs[tower] + + with place_on_device(self._get_data_device(tower)): + if self._has_bias: + inputs = append_homog(inputs) + self._squared_inputs.append(math_ops.square(inputs)) return super(FullyConnectedDiagonalFactor, self).make_covariance_update_op( ema_decay) - def _compute_new_cov(self, idx=0): - batch_size = array_ops.shape(self._squared_inputs)[0] - outputs_grad = self._outputs_grads[idx] + def _compute_new_cov(self, source, tower): + batch_size = array_ops.shape(self._squared_inputs[tower])[0] + outputs_grad = self._outputs_grads[source][tower] # The well-known special formula that uses the fact that the entry-wise # square of an outer product is the outer-product of the entry-wise squares. # The gradient is the outer product of the input and the output gradients, # so we just square both and then take their outer-product. new_cov = math_ops.matmul( - self._squared_inputs, + self._squared_inputs[tower], math_ops.square(outputs_grad), transpose_a=True) new_cov /= math_ops.cast(batch_size, new_cov.dtype) return new_cov + def _get_data_device(self, tower): + return self._inputs[tower].device + class ConvDiagonalFactor(DiagonalFactor): """FisherFactor for a diagonal approx of a convolutional layer's Fisher.""" @@ -874,11 +953,12 @@ class ConvDiagonalFactor(DiagonalFactor): """Creates a ConvDiagonalFactor object. Args: - inputs: Tensor of shape [batch_size, height, width, in_channels]. - Input activations to this layer. + inputs: List of Tensors of shape [batch_size, height, width, in_channels]. + Input activations to this layer. List index is towers. outputs_grads: List of Tensors, each of shape [batch_size, height, width, out_channels], which are the gradients of the loss - with respect to the layer's outputs. One Tensor for each "source". + with respect to the layer's outputs. First index is source, second + index is tower. filter_shape: Tuple of 4 ints: (kernel_height, kernel_width, in_channels, out_channels). Represents shape of kernel used in this layer. strides: The stride size in this layer (1-D Tensor of length 4). @@ -896,14 +976,15 @@ class ConvDiagonalFactor(DiagonalFactor): """ if not utils.is_data_format_channel_last(data_format): raise ValueError("Channel must be last.") - if inputs.shape.ndims != 4: - raise ValueError("inputs must be 4-D Tensor.") - if inputs.shape.as_list()[-1] != filter_shape[-2]: + if any(input_.shape.ndims != 4 for input_ in inputs): + raise ValueError("inputs must be a list of 4-D Tensors.") + if any(input_.shape.as_list()[-1] != filter_shape[-2] for input_ in inputs): raise ValueError("inputs and filter_shape must agree on in_channels.") for i, outputs_grad in enumerate(outputs_grads): - if outputs_grad.shape.ndims != 4: + if any(output_grad.shape.ndims != 4 for output_grad in outputs_grad): raise ValueError("outputs[%d] must be 4-D Tensor." % i) - if outputs_grad.shape.as_list()[-1] != filter_shape[-1]: + if any(output_grad.shape.as_list()[-1] != filter_shape[-1] + for output_grad in outputs_grad): raise ValueError( "outputs[%d] and filter_shape must agree on out_channels." % i) if len(strides) != 4: @@ -926,7 +1007,7 @@ class ConvDiagonalFactor(DiagonalFactor): @property def _var_scope(self): return "ff_convdiag_" + scope_string_from_params( - (self._inputs,) + tuple(self._outputs_grads)) + tuple(self._inputs) + tuple(nest.flatten(self._outputs_grads))) @property def _cov_shape(self): @@ -940,9 +1021,13 @@ class ConvDiagonalFactor(DiagonalFactor): def _num_sources(self): return len(self._outputs_grads) + @property + def _num_towers(self): + return len(self._inputs) + @property def _dtype(self): - return self._outputs_grads[0].dtype + return self._inputs[0].dtype def make_covariance_update_op(self, ema_decay): filter_height, filter_width, _, _ = self._filter_shape @@ -953,25 +1038,30 @@ class ConvDiagonalFactor(DiagonalFactor): rates = (1, 1, 1, 1) else: rates = tuple(self._dilations) - patches = array_ops.extract_image_patches( - self._inputs, - ksizes=[1, filter_height, filter_width, 1], - strides=self._strides, - rates=rates, - padding=self._padding) - if self._has_bias: - patches = append_homog(patches) + self._patches = [] + for tower in range(self._num_towers): + with place_on_device(self._get_data_device(tower)): + patches = array_ops.extract_image_patches( + self._inputs[tower], + ksizes=[1, filter_height, filter_width, 1], + strides=self._strides, + rates=rates, + padding=self._padding) + + if self._has_bias: + patches = append_homog(patches) - self._patches = patches + self._patches.append(patches) return super(ConvDiagonalFactor, self).make_covariance_update_op(ema_decay) - def _compute_new_cov(self, idx=0): - batch_size = array_ops.shape(self._patches)[0] - outputs_grad = self._outputs_grads[idx] + def _compute_new_cov(self, source, tower): + patches = self._patches[tower] + batch_size = array_ops.shape(patches)[0] + outputs_grad = self._outputs_grads[source][tower] - new_cov = self._convdiag_sum_of_squares(self._patches, outputs_grad) + new_cov = self._convdiag_sum_of_squares(patches, outputs_grad) new_cov /= math_ops.cast(batch_size, new_cov.dtype) return new_cov @@ -984,6 +1074,9 @@ class ConvDiagonalFactor(DiagonalFactor): outputs_grad) return math_ops.reduce_sum(math_ops.square(case_wise_gradients), axis=0) + def _get_data_device(self, tower): + return self._inputs[tower].device + class FullyConnectedKroneckerFactor(InverseProvidingFactor): """Kronecker factor for the input or output side of a fully-connected layer. @@ -995,9 +1088,9 @@ class FullyConnectedKroneckerFactor(InverseProvidingFactor): """Instantiate FullyConnectedKroneckerFactor. Args: - tensors: List of Tensors, each of shape [batch_size, n], one for each - source. The Tensors are typically either a layer's inputs or its - output's gradients. + tensors: List of list of Tensors, each of shape [batch_size, n]. The + Tensors are typically either a layer's inputs or its output's gradients. + The first list index is source, the second is tower. has_bias: bool. If True, append '1' to each row. """ # The tensor argument is either a tensor of input activations or a tensor of @@ -1009,27 +1102,34 @@ class FullyConnectedKroneckerFactor(InverseProvidingFactor): @property def _var_scope(self): return "ff_fckron_" + scope_string_from_params( - tuple(self._tensors) + (self._has_bias,)) + tuple(nest.flatten(self._tensors)) + (self._has_bias,)) @property def _cov_shape(self): - size = self._tensors[0].shape[1] + self._has_bias + size = self._tensors[0][0].shape[1] + self._has_bias return [size, size] @property def _num_sources(self): return len(self._tensors) + @property + def _num_towers(self): + return len(self._tensors[0]) + @property def _dtype(self): - return self._tensors[0].dtype + return self._tensors[0][0].dtype - def _compute_new_cov(self, idx=0): - tensor = self._tensors[idx] + def _compute_new_cov(self, source, tower): + tensor = self._tensors[source][tower] if self._has_bias: tensor = append_homog(tensor) return compute_cov(tensor) + def _get_data_device(self, tower): + return self._tensors[0][tower].device + class ConvInputKroneckerFactor(InverseProvidingFactor): r"""Kronecker factor for the input side of a convolutional layer. @@ -1053,8 +1153,8 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): """Initializes ConvInputKroneckerFactor. Args: - inputs: Tensor of shape [batch_size, ..spatial_input_size.., in_channels]. - Inputs to layer. + inputs: List of Tensors of shape [batch_size, ..spatial_input_size.., + in_channels]. Inputs to layer. List index is tower. filter_shape: List of ints. Contains [..spatial_filter_size.., in_channels, out_channels]. Shape of convolution kernel. padding: str. Padding method for layer. "SAME" or "VALID". @@ -1083,10 +1183,10 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): @property def _var_scope(self): - return "ff_convinkron_" + scope_string_from_params([ - self._inputs, self._filter_shape, self._strides, self._padding, - self._dilation_rate, self._data_format, self._has_bias - ]) + return "ff_convinkron_" + scope_string_from_params( + tuple(self._inputs) + + tuple((self._filter_shape, self._strides, self._padding, + self._dilation_rate, self._data_format, self._has_bias))) @property def _cov_shape(self): @@ -1099,19 +1199,24 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): def _num_sources(self): return 1 + @property + def _num_towers(self): + return len(self._inputs) + @property def _dtype(self): - return self._inputs.dtype + return self._inputs[0].dtype - def _compute_new_cov(self, idx=0): - if idx != 0: - raise ValueError("ConvInputKroneckerFactor only supports idx = 0") + def _compute_new_cov(self, source, tower): + assert source == 0 + + inputs = self._inputs[tower] # TODO(b/64144716): there is potential here for a big savings in terms of # memory use. if self._extract_patches_fn in [None, "extract_convolution_patches"]: patches = utils.extract_convolution_patches( - self._inputs, + inputs, self._filter_shape, padding=self._padding, strides=self._strides, @@ -1119,7 +1224,7 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): data_format=self._data_format) elif self._extract_patches_fn == "extract_image_patches": - assert self._inputs.shape.ndims == 4 + assert inputs.shape.ndims == 4 assert len(self._filter_shape) == 4 assert len(self._strides) == 4, self._strides if self._dilation_rate is None: @@ -1129,7 +1234,7 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): assert len(rates) == 4 assert rates[0] == rates[-1] == 1 patches = array_ops.extract_image_patches( - self._inputs, + inputs, ksizes=[1] + list(self._filter_shape[0:-2]) + [1], strides=self._strides, rates=rates, @@ -1139,7 +1244,7 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): assert self._strides in [None, [1, 1, 1, 1], (1, 1, 1, 1)] assert self._filter_shape[0] == self._filter_shape[1] == 1 patches = utils.extract_pointwise_conv2d_patches( - self._inputs, self._filter_shape, data_format=None) + inputs, self._filter_shape, data_format=None) else: raise NotImplementedError(self._extract_patches_fn) @@ -1164,6 +1269,9 @@ class ConvInputKroneckerFactor(InverseProvidingFactor): # (Tilde omitted over A for clarity.) return compute_cov(patches_flat) + def _get_data_device(self, tower): + return self._inputs[tower].device + class ConvOutputKroneckerFactor(InverseProvidingFactor): r"""Kronecker factor for the output side of a convolutional layer. @@ -1180,9 +1288,9 @@ class ConvOutputKroneckerFactor(InverseProvidingFactor): """Initializes ConvOutputKroneckerFactor. Args: - outputs_grads: list of Tensors. Each Tensor is of shape - [batch_size, ..spatial_input_size.., out_channels]. One Tensor per - source. + outputs_grads: List of list of Tensors. Each Tensor is of shape + [batch_size, ..spatial_input_size.., out_channels]. First list index + is source, the second is tower. data_format: None or str. Format of outputs_grads. Raises: @@ -1190,13 +1298,14 @@ class ConvOutputKroneckerFactor(InverseProvidingFactor): """ if not utils.is_data_format_channel_last(data_format): raise ValueError("Channel must be last.") - self._out_channels = outputs_grads[0].shape.as_list()[-1] + self._out_channels = outputs_grads[0][0].shape.as_list()[-1] self._outputs_grads = outputs_grads super(ConvOutputKroneckerFactor, self).__init__() @property def _var_scope(self): - return "ff_convoutkron_" + scope_string_from_params(self._outputs_grads) + return "ff_convoutkron_" + scope_string_from_params( + nest.flatten(self._outputs_grads)) @property def _cov_shape(self): @@ -1207,12 +1316,16 @@ class ConvOutputKroneckerFactor(InverseProvidingFactor): def _num_sources(self): return len(self._outputs_grads) + @property + def _num_towers(self): + return len(self._outputs_grads[0]) + @property def _dtype(self): - return self._outputs_grads[0].dtype + return self._outputs_grads[0][0].dtype - def _compute_new_cov(self, idx=0): - outputs_grad = self._outputs_grads[idx] + def _compute_new_cov(self, source, tower): + outputs_grad = self._outputs_grads[source][tower] # reshaped_tensor below is the matrix DS_l defined in the KFC paper # (tilde omitted over S for clarity). It has shape M|T| x I, where @@ -1225,6 +1338,9 @@ class ConvOutputKroneckerFactor(InverseProvidingFactor): # (Tilde omitted over S for clarity.) return compute_cov(reshaped_tensor) + def _get_data_device(self, tower): + return self._outputs_grads[0][tower].device + class FullyConnectedMultiKF(FullyConnectedKroneckerFactor): """Kronecker factor for a fully connected layer used multiple times.""" @@ -1236,9 +1352,11 @@ class FullyConnectedMultiKF(FullyConnectedKroneckerFactor): """Constructs a new `FullyConnectedMultiKF`. Args: - tensors: List of Tensors of shape, each of shape [batch_size, n]. Each of - these tensors is usually a layer's inputs or its output's gradients. - The list is over sources. + tensors: List of list of Tensors of shape, each of shape + [num_uses * batch_size, n], and is a reshape version of a Tensor of + shape [num_uses, batch_size, n]. Each of these tensors is usually a + layer's inputs or its output's gradients. The first list index is + sources, the second is towers. num_uses: int. The number of time-steps / uses. has_bias: bool. If True, '1' is appended to each row. """ @@ -1262,16 +1380,24 @@ class FullyConnectedMultiKF(FullyConnectedKroneckerFactor): @property def _var_scope(self): return "ff_fc_multi_" + scope_string_from_params( - tuple(self._tensors) + (self._num_timesteps, self._has_bias,)) + tuple(nest.flatten(self._tensors)) + + (self._num_timesteps, self._has_bias,)) def make_covariance_update_op(self, ema_decay): op = super(FullyConnectedMultiKF, self).make_covariance_update_op(ema_decay) if self._cov_dt1 is not None: - new_cov_dt1_contribs = tuple(self._compute_new_cov_dt1(idx) - for idx in range(self._num_sources)) - new_cov_dt1 = math_ops.add_n(new_cov_dt1_contribs) + new_cov_dt1_contribs = [] + for source in range(self._num_sources): + for tower in range(self._num_towers): + with place_on_device(self._get_data_device(tower)): + new_cov_dt1_contribs.append(self._compute_new_cov_dt1(source, + tower)) + + new_cov_dt1 = (math_ops.add_n(new_cov_dt1_contribs) + / float(self._num_towers)) + op2 = moving_averages.assign_moving_average( self._cov_dt1, new_cov_dt1, ema_decay, zero_debias=ZERO_DEBIAS) @@ -1284,8 +1410,8 @@ class FullyConnectedMultiKF(FullyConnectedKroneckerFactor): return op - def _compute_new_cov_dt1(self, idx=0): # pylint: disable=missing-docstring - tensor = self._tensors[idx] + def _compute_new_cov_dt1(self, source, tower): # pylint: disable=missing-docstring + tensor = self._tensors[source][tower] if self._has_bias: # This appending is technically done twice (the other time is for # _compute_new_cov()) @@ -1303,9 +1429,12 @@ class FullyConnectedMultiKF(FullyConnectedKroneckerFactor): return compute_cov( tensor_future, tensor_right=tensor_present, normalizer=total_len) + def _get_data_device(self, tower): + return self._tensors[0][tower].device + @property def _vec_shape(self): - size = self._tensors[0].shape[1] + self._has_bias + size = self._tensors[0][0].shape[1] + self._has_bias return [size] def get_option1quants(self, damping_func): diff --git a/tensorflow/contrib/kfac/python/ops/layer_collection.py b/tensorflow/contrib/kfac/python/ops/layer_collection.py index 7727c607db..586a004f88 100644 --- a/tensorflow/contrib/kfac/python/ops/layer_collection.py +++ b/tensorflow/contrib/kfac/python/ops/layer_collection.py @@ -390,7 +390,7 @@ class LayerCollection(object): if name in self._loss_dict: raise KeyError( "Loss function named {} already exists. Set reuse=True to append " - "another minibatch/tower.".format(name)) + "another tower.".format(name)) loss_list = [] self._loss_dict[name] = loss_list @@ -596,7 +596,7 @@ class LayerCollection(object): vocab_size = int(params.shape[0]) block = self.register_block( params, block_type(self, vocab_size), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, 1) @@ -637,7 +637,7 @@ class LayerCollection(object): has_bias = isinstance(params, (tuple, list)) block = self.register_block(params, block_type(self, has_bias=has_bias), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, 1) @@ -716,7 +716,7 @@ class LayerCollection(object): else: raise NotImplementedError(approx) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, 1) @@ -774,7 +774,7 @@ class LayerCollection(object): dilation_rate=dilation_rate, data_format=data_format), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, 1) @@ -830,7 +830,7 @@ class LayerCollection(object): rate=rate, data_format=data_format), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, 1) @@ -913,7 +913,7 @@ class LayerCollection(object): Args: params: Tensor or tuple of Tensors corresponding to the parameters. - batch_size: 0-D Tensor. Size of the minibatch. + batch_size: 0-D Tensor. Size of the minibatch (for this tower). approx: str or None. It not None, must be one of "full" or "diagonal". The Fisher approximation to use. If None the default value is used. (Default: None) @@ -932,7 +932,7 @@ class LayerCollection(object): _GENERIC_APPROX_TO_BLOCK_TYPES) block = self.register_block(params, block_type(self, params), reuse=reuse) - block.register_additional_minibatch(batch_size) + block.register_additional_tower(batch_size) self._add_uses(params, float("inf")) @@ -952,14 +952,14 @@ class LayerCollection(object): inputs: A list of Tensors, each of shape [batch_size, input_size]. Inputs to layer. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). OR, can be single Tensor, of - shape [batch_size * num_uses, input_size], which is a reshaped version - of a Tensor of shape [batch_size, num_uses, input_size]. + shape [num_uses * batch_size , input_size], which is a reshaped version + of a Tensor of shape [num_uses, batch_size, input_size]. outputs: A list of Tensors, the same length as 'inputs', each of shape [batch_size, output_size]. Outputs produced by layer. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). Needs to correspond with the order used in 'inputs'. OR, can be - a single Tensor of shape [batch_size * num_uses, output_size], which is - a reshaped version of a Tensor of shape [batch_size, num_uses, + a single Tensor of shape [num_uses * batch_size, output_size], which is + a reshaped version of a Tensor of shape [num_uses, batch_size, output_size]. num_uses: int or None. The number uses/time-steps in the graph where the layer appears. Only needed if both inputs and outputs are given in the @@ -989,7 +989,7 @@ class LayerCollection(object): block = self.register_block(params, block_type(self, has_bias=has_bias, num_uses=num_uses), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) assert len(inputs) == len(outputs) self._add_uses(params, len(inputs)) @@ -1017,16 +1017,16 @@ class LayerCollection(object): inputs: A list of Tensors, each of shape [batch_size, height, width, in_channels]. Inputs to layer. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). OR, can be single - Tensor, of shape [batch_size * num_uses, height, width, in_channels], - which is a reshaped version of a Tensor of shape [batch_size, num_uses, + Tensor, of shape [num_uses * batch_size, height, width, in_channels], + which is a reshaped version of a Tensor of shape [num_uses, batch_size, height, width, in_channels]. outputs: A list of Tensors, each of shape [batch_size, height, width, out_channels]. Output produced by layer. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). Needs to correspond with the order used in 'inputs'. OR, can be a - single Tensor, of shape [batch_size*num_uses, height, width, + single Tensor, of shape [num_uses * batch_size, height, width, out_channels], which is a reshaped version of a Tensor of shape - [batch_size, num_uses, height, width, out_channels]. + [num_uses, batch_size, height, width, out_channels]. num_uses: int or None. The number uses/time-steps in the graph where the layer appears. Only needed if both inputs and outputs are given in the single Tensor format. (Default: None) @@ -1065,7 +1065,7 @@ class LayerCollection(object): num_uses=num_uses), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) assert len(inputs) == len(outputs) self._add_uses(params, len(inputs)) @@ -1088,15 +1088,15 @@ class LayerCollection(object): inputs: A list of Tensors, each of shape [batch_size, input_size] and dtype int32. Indices into embedding matrix. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). - OR, can be single Tensor, of shape [batch_size * num_uses, input_size], - which is a reshaped version of a Tensor of shape [batch_size, num_uses, + OR, can be single Tensor, of shape [num_uses, batch_size, input_size], + which is a reshaped version of a Tensor of shape [num_uses, batch_size, input_size]. outputs: A list of Tensors, each of shape [batch_size, embedding_size]. Outputs produced by layer. The list indexes each use in the graph (which might correspond to a "time-step" in an RNN). Needs to correspond with the order used in 'inputs'. OR, can be a - single Tensor, of shape [batch_size*num_uses, embedding_size], which - is a reshaped version of a Tensor of shape [batch_size, num_uses, + single Tensor, of shape [num_uses * batch_size, embedding_size], which + is a reshaped version of a Tensor of shape [num_uses, batch_size, embedding_size]. num_uses: int or None. The number uses/time-steps in the graph where the layer appears. Only needed if both inputs and outputs are given in the @@ -1127,7 +1127,7 @@ class LayerCollection(object): block = self.register_block( params, block_type(self, vocab_size, num_uses=num_uses), reuse=reuse) - block.register_additional_minibatch(inputs, outputs) + block.register_additional_tower(inputs, outputs) self._add_uses(params, len(inputs)) diff --git a/tensorflow/contrib/kfac/python/ops/utils.py b/tensorflow/contrib/kfac/python/ops/utils.py index c9de0c7270..b6f42815e7 100644 --- a/tensorflow/contrib/kfac/python/ops/utils.py +++ b/tensorflow/contrib/kfac/python/ops/utils.py @@ -649,9 +649,6 @@ class PartitionedTensor(object): def dtype(self): return self.tensors[0].dtype - def devices(self): - return set(tensor.device for tensor in self.tensors) - def __str__(self): return "PartitionedTensor([%s, ...], dtype=%s, shape=%s)" % ( self.tensors[0].name, self.dtype.name, tuple(self.shape.as_list())) @@ -681,6 +678,15 @@ class PartitionedTensor(object): self._concats[result.device] = result return self._concats[result.device] + @property + def device(self): + # PartitionedTensors in general do not live on a single device. If the + # device cannot be determined unambiguously this property will return None. + device = self.tensors[0].device + if all(tensor.device == device for tensor in self.tensors): + return device + return None + ops.register_tensor_conversion_function( PartitionedTensor, -- GitLab From 85bec0af5e4bd036a9cb922c794bbe7191f7b76d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 08:16:59 -0700 Subject: [PATCH 262/906] Optimized quantized fully-connected op for LSTMs. PiperOrigin-RevId: 190617310 --- .../internal/optimized/optimized_ops.h | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h index f08d9d6d57..e079ff3f4c 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h @@ -802,21 +802,20 @@ inline void FullyConnected(const uint8* input_data, const Dims<4>& input_dims, input_offset, output_pipeline); } -inline void FullyConnected(const uint8* input_data, const Dims<4>& input_dims, - int32 input_offset, const uint8* filter_data, - const Dims<4>& filter_dims, int32 filter_offset, - const int32* bias_data, const Dims<4>& bias_dims, - int32 output_offset, int32 output_multiplier, - int output_shift, int32 output_activation_min, - int32 output_activation_max, int16* output_data, - const Dims<4>& output_dims, - gemmlowp::GemmContext* gemm_context) { +inline void FullyConnected( + const uint8* input_data, const Dims<4>& input_dims, int32 input_offset, + const uint8* filter_data, const Dims<4>& filter_dims, int32 filter_offset, + const int32* bias_data_int32, const Dims<4>& bias_dims, int32 output_offset, + int32 output_multiplier, int output_shift, int32 output_activation_min, + int32 output_activation_max, int16* output_data, const Dims<4>& output_dims, + gemmlowp::GemmContext* gemm_context) { gemmlowp::ScopedProfilingLabel label("FullyConnected/Uint8Int16"); // This is a copy of the reference implementation. We do not currently have a // properly optimized version. (void)gemm_context; // only used in properly optimized code. TFLITE_DCHECK_LE(output_activation_min, output_activation_max); TFLITE_DCHECK_EQ(output_offset, 0); + // TODO(benoitjacob): This really should be: // const int batches = ArraySize(output_dims, 1); // but the current --variable_batch hack consists in overwriting the 3rd @@ -828,30 +827,49 @@ inline void FullyConnected(const uint8* input_data, const Dims<4>& input_dims, const int accum_depth = ArraySize(filter_dims, 0); TFLITE_DCHECK(IsPackedWithoutStrides(input_dims)); TFLITE_DCHECK(IsPackedWithoutStrides(filter_dims)); - for (int b = 0; b < batches; ++b) { - for (int out_c = 0; out_c < output_depth; ++out_c) { - // Internal accumulation. - // Initialize accumulator with the bias-value. - int32 accum = bias_data[out_c]; - // Accumulation loop. - for (int d = 0; d < accum_depth; ++d) { - int16 input_val = input_data[b * accum_depth + d] + input_offset; - int16 filter_val = filter_data[out_c * accum_depth + d] + filter_offset; - accum += filter_val * input_val; - } - // Down-scale the final int32 accumulator to the scale used by our - // (16-bit, typically 3 integer bits) fixed-point format. The quantized - // multiplier and shift here have been pre-computed offline - // (e.g. by toco). - accum = MultiplyByQuantizedMultiplier(accum, output_multiplier, - -output_shift); - // Saturate, cast to int16, and store to output array. - accum = std::max(accum, output_activation_min - output_offset); - accum = std::min(accum, output_activation_max - output_offset); - accum += output_offset; - output_data[out_c + output_depth * b] = accum; - } + + // Implementation of the fully connected node suited to the inside of an LSTM + // cell. The operands are 8-bit integers, the accumulators are internally + // 32bit integers, and the output is 16-bit fixed-point with 3 integer bits so + // the output range is [-2^3, 2^3] == [-8, 8]. The rationale for that + // is explained in the function comment above. +#ifdef GEMMLOWP_NEON + if (batches == 1 && !(output_depth % 4) && !(accum_depth % 8) && + input_offset == -128 && output_activation_min == -32768 && + output_activation_max == 32767) { + GEMVForLstmCell(input_data, input_dims, filter_data, filter_dims, + filter_offset, bias_data_int32, bias_dims, + output_multiplier, -output_shift, output_data, output_dims); + return; } +#endif + gemmlowp::MatrixMap weights_matrix( + filter_data, output_depth, accum_depth); + gemmlowp::MatrixMap input_matrix( + input_data, accum_depth, batches); + gemmlowp::MatrixMap output_matrix( + output_data, output_depth, batches); + typedef gemmlowp::VectorMap + ColVectorMap; + ColVectorMap bias_vector(bias_data_int32, output_depth); + gemmlowp::OutputStageBiasAddition bias_addition_stage; + bias_addition_stage.bias_vector = bias_vector; + gemmlowp::OutputStageScaleInt32ByFixedPointAndExponent scale_stage; + scale_stage.result_offset_after_shift = 0; + scale_stage.result_fixedpoint_multiplier = output_multiplier; + // Note that this shift is negated wrt ordinary FC. + scale_stage.result_exponent = -output_shift; + gemmlowp::OutputStageClamp clamp_stage; + clamp_stage.min = output_activation_min; + clamp_stage.max = output_activation_max; + gemmlowp::OutputStageSaturatingCastToInt16 saturating_cast_int16_stage; + auto output_pipeline = + std::make_tuple(bias_addition_stage, scale_stage, clamp_stage, + saturating_cast_int16_stage); + gemmlowp::GemmWithOutputPipeline( + gemm_context, weights_matrix, input_matrix, &output_matrix, filter_offset, + input_offset, output_pipeline); } // legacy, for compatibility with old checked-in code -- GitLab From cf24990855b4418f86ffc5cfe65b502cd0d8b924 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 27 Mar 2018 08:33:22 -0700 Subject: [PATCH 263/906] Automated g4 rollback of changelist 188385868 PiperOrigin-RevId: 190618988 --- tensorflow/compiler/tests/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD index bbb6089ea8..1c5a8f8e69 100644 --- a/tensorflow/compiler/tests/BUILD +++ b/tensorflow/compiler/tests/BUILD @@ -542,7 +542,6 @@ tf_xla_py_test( size = "medium", srcs = ["spacetobatch_op_test.py"], shard_count = 3, - tags = ["notsan"], deps = [ ":xla_test", "//tensorflow/python:array_ops", -- GitLab From ff9040f645a042ef62782be0eed8b4597e80ce6c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 09:20:54 -0700 Subject: [PATCH 264/906] Flush the output of print (fixes out-of-order prints in public colab) PiperOrigin-RevId: 190624708 --- tensorflow/contrib/autograph/utils/builtins.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/autograph/utils/builtins.py b/tensorflow/contrib/autograph/utils/builtins.py index 4ab32ee47d..c6af0e4d13 100644 --- a/tensorflow/contrib/autograph/utils/builtins.py +++ b/tensorflow/contrib/autograph/utils/builtins.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import sys + import six from tensorflow.contrib.autograph.utils import py_func @@ -97,7 +99,13 @@ def dynamic_print(*values): if all(map(is_tf_print_compatible, values)): return logging_ops.Print(1, values) - return py_func.wrap_py_func(print, None, values, use_dummy_return=True) + + def flushed_print(*vals): + print(*vals) + sys.stdout.flush() + + return py_func.wrap_py_func( + flushed_print, None, values, use_dummy_return=True) def dynamic_dataset(iterated): -- GitLab From 2ad47da4fb9896290eb9bc87fe809a4138269f2c Mon Sep 17 00:00:00 2001 From: brett koonce Date: Tue, 27 Mar 2018 09:24:40 -0700 Subject: [PATCH 265/906] Seq2seq minorsp (#18010) * contrib/seq2seq: minor spelling tweaks * contrib/timeseries: minor spelling tweaks * contrib/slim: minor spelling tweaks --- tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc | 2 +- .../contrib/seq2seq/python/ops/attention_wrapper.py | 8 ++++---- .../contrib/seq2seq/python/ops/beam_search_decoder.py | 6 +++--- .../contrib/slim/python/slim/data/parallel_reader.py | 4 ++-- .../contrib/slim/python/slim/data/prefetch_queue.py | 4 ++-- .../contrib/slim/python/slim/data/tfexample_decoder.py | 2 +- .../contrib/timeseries/python/timeseries/ar_model.py | 2 +- .../contrib/timeseries/python/timeseries/math_utils.py | 2 +- .../python/timeseries/state_space_models/varma.py | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc index dfa12e873a..a9a32b7b25 100644 --- a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc +++ b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc @@ -74,7 +74,7 @@ class GatherTreeOp : public OpKernel { ctx, step_ids_shape.dim_size(1) == max_sequence_lengths.shape().dim_size(0), errors::InvalidArgument("batch size dimensions step_ids.shape[1] and " - "max_seqeuence_lengths.shape[0] must match. " + "max_sequence_lengths.shape[0] must match. " "but shapes are: ", step_ids_shape.DebugString(), " and ", max_sequence_lengths.shape().DebugString())); diff --git a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py index 9ff8a343f1..be53779826 100644 --- a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py +++ b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py @@ -736,7 +736,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): """Base attention mechanism for monotonic attention. Simply overrides the initial_alignments function to provide a dirac - distribution,which is needed in order for the monotonic attention + distribution, which is needed in order for the monotonic attention distributions to have the correct behavior. """ @@ -763,7 +763,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Bahadanau-style energy function. - This type of attention encorces a monotonic constraint on the attention + This type of attention enforces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -867,7 +867,7 @@ class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Luong-style energy function. - This type of attention encorces a monotonic constraint on the attention + This type of attention enforces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -1133,7 +1133,7 @@ class AttentionWrapper(rnn_cell_impl.RNNCell): output_attention: Python bool. If `True` (default), the output at each time step is the attention value. This is the behavior of Luong-style attention mechanisms. If `False`, the output at each time step is - the output of `cell`. This is the beahvior of Bhadanau-style + the output of `cell`. This is the behavior of Bhadanau-style attention mechanisms. In both cases, the `attention` tensor is propagated to the next time step via the state and is used there. This flag only controls whether the attention mechanism is propagated diff --git a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py index a26107b0d7..184144f64a 100644 --- a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py +++ b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py @@ -821,9 +821,9 @@ def _get_scores(log_probs, sequence_lengths, length_penalty_weight): Returns: The scores normalized by the length_penalty. """ - length_penality_ = _length_penalty( + length_penalty_ = _length_penalty( sequence_lengths=sequence_lengths, penalty_factor=length_penalty_weight) - return log_probs / length_penality_ + return log_probs / length_penalty_ def _length_penalty(sequence_lengths, penalty_factor): @@ -860,7 +860,7 @@ def _mask_probs(probs, eos_token, finished): unfinished beams remain unchanged. Args: - probs: Log probabiltiies of shape `[batch_size, beam_width, vocab_size]` + probs: Log probabilities of shape `[batch_size, beam_width, vocab_size]` eos_token: An int32 id corresponding to the EOS token to allocate probability to. finished: A boolean tensor of shape `[batch_size, beam_width]` that diff --git a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py index b3343aef47..99ad487630 100644 --- a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py +++ b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py @@ -115,8 +115,8 @@ class ParallelReader(io_ops.ReaderBase): reader needs to start reading from a new file since it has finished with the previous file). - A queue runner for enqueing in the `common_queue` is automatically added to - the TF QueueRunners collection. + A queue runner for enqueuing in the `common_queue` is automatically added + to the TF QueueRunners collection. Args: queue: A Queue or a mutable string Tensor representing a handle diff --git a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py index 37e9c4754c..62bd200361 100644 --- a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py +++ b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py @@ -36,9 +36,9 @@ def prefetch_queue(tensors, dynamic_pad=False, shared_name=None, name=None): - """Creates a queue to prefetech tensors from `tensors`. + """Creates a queue to prefetch tensors from `tensors`. - A queue runner for enqueing tensors into the prefetch_queue is automatically + A queue runner for enqueuing tensors into the prefetch_queue is automatically added to the TF QueueRunners collection. Example: diff --git a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py index b3b61e1dfe..f2d31dc8db 100644 --- a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py +++ b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py @@ -124,7 +124,7 @@ class BoundingBox(ItemHandler): super(BoundingBox, self).__init__(self._full_keys) def tensors_to_item(self, keys_to_tensors): - """Maps the given dictionary of tensors to a contatenated list of bboxes. + """Maps the given dictionary of tensors to a concatenated list of bboxes. Args: keys_to_tensors: a mapping of TF-Example keys to parsed tensors. diff --git a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py index ff140efd48..4f6527a546 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py +++ b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py @@ -70,7 +70,7 @@ class ARModel(model.TimeSeriesModel): input_window_size: Number of past time steps of data to look at when doing the regression. output_window_size: Number of future time steps to predict. Note that - setting it to > 1 empiricaly seems to give a better fit. + setting it to > 1 empirically seems to give a better fit. num_features: number of input features per time step. num_time_buckets: Number of buckets into which to divide (time % periodicity) for generating time based features. diff --git a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py index 23452a81c3..26793c80bf 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py +++ b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py @@ -185,7 +185,7 @@ def batch_matrix_pow(matrices, powers): { matmul(A, power(matmul(A, A), (p - 1) / 2)) for odd p power(A, 0) = I - The power(A, 0) = I case is handeled by starting with accumulator set to the + The power(A, 0) = I case is handled by starting with accumulator set to the identity matrix; matrices with zero residual powers are passed through unchanged. diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py index 1afc58cfb2..6746dd7b43 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py @@ -107,7 +107,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state transition matrix. It has shape - [self.state_dimendion, self.state_dimension]. + [self.state_dimension, self.state_dimension]. """ # Pad any unused AR blocks with zeros. The extra state is necessary if # ma_order >= ar_order. @@ -127,7 +127,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state noise transform matrix. It has shape - [self.state_dimendion, self.num_features]. + [self.state_dimension, self.num_features]. """ # Noise is broadcast, through the moving average coefficients, to # un-observed parts of the latent state. -- GitLab From fdec18588d7f8b5f6383601f1030ed71f634d1c0 Mon Sep 17 00:00:00 2001 From: James Keeling Date: Tue, 27 Mar 2018 09:36:52 -0700 Subject: [PATCH 266/906] Prevent warning every time someone imports contrib.learn.datasets.base Everything in contrib/learn/python/learn/datasets/base.py has been deprecated. One of the function in there is a decorator, retry. Because another function in that file is decorated with retry, the function is called upon import, which prints a warning. I have fixed this by adding a private function, _internal_retry, which is used internally, and redefining retry to simply call this. That way, using retry in user-code will still print the deprecated warning, but it's not printed upon every import. I also cleaned up the docstrings slightly. PiperOrigin-RevId: 190626717 --- .../learn/python/learn/datasets/base.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/learn/python/learn/datasets/base.py b/tensorflow/contrib/learn/python/learn/datasets/base.py index 3b5c9b97c0..4676eedb20 100644 --- a/tensorflow/contrib/learn/python/learn/datasets/base.py +++ b/tensorflow/contrib/learn/python/learn/datasets/base.py @@ -139,15 +139,48 @@ def retry(initial_delay, Args: initial_delay: the initial delay. + max_delay: the maximum delay allowed (actual max is + max_delay * (1 + jitter). factor: each subsequent retry, the delay is multiplied by this value. (must be >= 1). jitter: to avoid lockstep, the returned delay is multiplied by a random number between (1-jitter) and (1+jitter). To add a 20% jitter, set jitter = 0.2. Must be < 1. + is_retriable: (optional) a function that takes an Exception as an argument + and returns true if retry should be applied. + + Returns: + A function that wraps another function to automatically retry it. + """ + return _internal_retry( + initial_delay=initial_delay, + max_delay=max_delay, + factor=factor, + jitter=jitter, + is_retriable=is_retriable) + + +def _internal_retry(initial_delay, + max_delay, + factor=2.0, + jitter=0.25, + is_retriable=None): + """Simple decorator for wrapping retriable functions, for internal use only. + + Args: + initial_delay: the initial delay. max_delay: the maximum delay allowed (actual max is max_delay * (1 + jitter). + factor: each subsequent retry, the delay is multiplied by this value. + (must be >= 1). + jitter: to avoid lockstep, the returned delay is multiplied by a random + number between (1-jitter) and (1+jitter). To add a 20% jitter, set + jitter = 0.2. Must be < 1. is_retriable: (optional) a function that takes an Exception as an argument and returns true if retry should be applied. + + Returns: + A function that wraps another function to automatically retry it. """ if factor < 1: raise ValueError('factor must be >= 1; was %f' % (factor,)) @@ -195,7 +228,7 @@ def _is_retriable(e): @deprecated(None, 'Please use urllib or similar directly.') -@retry(initial_delay=1.0, max_delay=16.0, is_retriable=_is_retriable) +@_internal_retry(initial_delay=1.0, max_delay=16.0, is_retriable=_is_retriable) def urlretrieve_with_retry(url, filename=None): return urllib.request.urlretrieve(url, filename) -- GitLab From bba3c8f13516b4d4df83f179913376ab36807f9f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 10:04:41 -0700 Subject: [PATCH 267/906] import tpu profiler analysis grpc python stub to tensorflow. PiperOrigin-RevId: 190630641 --- tensorflow/contrib/tpu/BUILD | 2 ++ tensorflow/contrib/tpu/profiler/BUILD | 4 +--- .../contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py | 2 +- tensorflow/contrib/tpu/python/profiler/__init__.py | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD index eea19e9465..95dc6f5ced 100644 --- a/tensorflow/contrib/tpu/BUILD +++ b/tensorflow/contrib/tpu/BUILD @@ -119,6 +119,8 @@ py_library( srcs = ["python/profiler/__init__.py"], srcs_version = "PY2AND3", deps = [ + "//tensorflow/contrib/tpu/profiler:tpu_profiler_analysis_pb2_grpc", + "//tensorflow/contrib/tpu/profiler:tpu_profiler_analysis_proto_py", "//tensorflow/contrib/tpu/profiler:trace_events_proto_py", "//tensorflow/python:util", ], diff --git a/tensorflow/contrib/tpu/profiler/BUILD b/tensorflow/contrib/tpu/profiler/BUILD index 0a52d0b13b..56ddd7eff1 100644 --- a/tensorflow/contrib/tpu/profiler/BUILD +++ b/tensorflow/contrib/tpu/profiler/BUILD @@ -127,7 +127,5 @@ py_library( srcs = ["tpu_profiler_analysis_pb2_grpc.py"], srcs_version = "PY2AND3", visibility = ["//visibility:public"], - deps = [ - ":tpu_profiler_analysis_proto_py", - ], + deps = [":tpu_profiler_analysis_proto_py"], ) diff --git a/tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py b/tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py index c28fef22a9..8f51488288 100644 --- a/tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py +++ b/tensorflow/contrib/tpu/profiler/tpu_profiler_analysis_pb2_grpc.py @@ -22,7 +22,7 @@ from __future__ import print_function import grpc -from third_party.tensorflow.contrib.tpu.profiler import tpu_profiler_analysis_pb2 as third__party_dot_tensorflow_dot_contrib_dot_tpu_dot_profiler_dot_tpu__profiler__analysis__pb2 +from tensorflow.contrib.tpu.profiler import tpu_profiler_analysis_pb2 as third__party_dot_tensorflow_dot_contrib_dot_tpu_dot_profiler_dot_tpu__profiler__analysis__pb2 class TPUProfileAnalysisStub(object): diff --git a/tensorflow/contrib/tpu/python/profiler/__init__.py b/tensorflow/contrib/tpu/python/profiler/__init__.py index bde13f0527..15ce6aceec 100644 --- a/tensorflow/contrib/tpu/python/profiler/__init__.py +++ b/tensorflow/contrib/tpu/python/profiler/__init__.py @@ -20,6 +20,7 @@ from __future__ import division from __future__ import print_function # pylint: disable=wildcard-import,unused-import +from tensorflow.contrib.tpu.profiler.tpu_profiler_analysis_pb2 import * from tensorflow.contrib.tpu.profiler.trace_events_pb2 import * # pylint: enable=wildcard-import,unused-import -- GitLab From 1712002ad02f044f7569224bf465e0ea00e6a6c4 Mon Sep 17 00:00:00 2001 From: Nick Felt Date: Tue, 27 Mar 2018 10:11:49 -0700 Subject: [PATCH 268/906] Update tb-nightly dep to >= 1.8.0a0, < 1.9.0a0 (#18009) Synchronize tf-nightly dep on current tb-nightly. --- tensorflow/tools/pip_package/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index ff30016cc2..3e4f9b0fdd 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -62,7 +62,7 @@ else: if 'tf_nightly' in project_name: for i, pkg in enumerate(REQUIRED_PACKAGES): if 'tensorboard' in pkg: - REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.7.0a0, < 1.8.0a0' + REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.8.0a0, < 1.9.0a0' break # weakref.finalize and enum were introduced in Python 3.4 -- GitLab From bdaa9a0ce84798eb13b97de664451cd87c3f8210 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 10:20:05 -0700 Subject: [PATCH 269/906] Internal cleanup. PiperOrigin-RevId: 190633067 --- tensorflow/contrib/lite/testing/BUILD | 2 +- tensorflow/contrib/lite/testing/generated_examples_zip_test.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index 555ea90034..dc9492f5e2 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -29,7 +29,7 @@ gen_zipped_test_files( "exp.zip", "fully_connected.zip", "fused_batch_norm.zip", - "gather.zip", + # "gather.zip", #TODO(b/76437794) "global_batch_norm.zip", "l2_pool.zip", "l2norm.zip", diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc index ba2d259462..08354b762c 100644 --- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc @@ -244,7 +244,7 @@ INSTANTIATE_TESTS(div) INSTANTIATE_TESTS(exp) INSTANTIATE_TESTS(fully_connected) INSTANTIATE_TESTS(fused_batch_norm) -INSTANTIATE_TESTS(gather) +// INSTANTIATE_TESTS(gather) //TODO(b/76437794) INSTANTIATE_TESTS(global_batch_norm) INSTANTIATE_TESTS(l2_pool) INSTANTIATE_TESTS(l2norm) -- GitLab From f04822a1bb5b1bc50e8b41d4bc3a04d0641d93e1 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Tue, 27 Mar 2018 11:09:50 -0700 Subject: [PATCH 270/906] Match behavior of py_func in graph and eager. PiperOrigin-RevId: 190641841 --- tensorflow/python/ops/script_ops.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/script_ops.py b/tensorflow/python/ops/script_ops.py index 1b4111bca6..96fb024715 100644 --- a/tensorflow/python/ops/script_ops.py +++ b/tensorflow/python/ops/script_ops.py @@ -334,7 +334,11 @@ def py_func(func, inp, Tout, stateful=True, name=None): result = func(*[x.numpy() for x in inp]) result = nest.flatten(result) - return [x if x is None else ops.convert_to_tensor(x) for x in result] + result = [x if x is None else ops.convert_to_tensor(x) for x in result] + if len(result) == 1: + # Mimic the automatic unwrapping in graph-mode py_func + result, = result + return result return _internal_py_func( func=func, inp=inp, Tout=Tout, stateful=stateful, eager=False, name=name) -- GitLab From 5c1ad16bfd265da2268ab1820d411dfaeaca5e05 Mon Sep 17 00:00:00 2001 From: Dimitris Vardoulakis Date: Tue, 27 Mar 2018 11:27:11 -0700 Subject: [PATCH 271/906] Fix: Clamp takes three arguments after computation, not arbitrarily many. PiperOrigin-RevId: 190644837 --- tensorflow/docs_src/performance/xla/operation_semantics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/docs_src/performance/xla/operation_semantics.md b/tensorflow/docs_src/performance/xla/operation_semantics.md index 5e39e710a0..4d12c7ab6d 100644 --- a/tensorflow/docs_src/performance/xla/operation_semantics.md +++ b/tensorflow/docs_src/performance/xla/operation_semantics.md @@ -241,7 +241,7 @@ See also Clamps an operand to within the range between a minimum and maximum value. - `Clamp(computation, args...)` + `Clamp(computation, min, operand, max)` | Arguments | Type | Semantics | | ------------- | ----------------------- | -------------------------------- | -- GitLab From a185f4a4c203853506b0b1989f2322210ef27660 Mon Sep 17 00:00:00 2001 From: Amit Patankar Date: Tue, 27 Mar 2018 11:35:18 -0700 Subject: [PATCH 272/906] Trying to fix libtensorflow GPU build. CUDNN path error. Invalid path to cuDNN 7 toolkit. None of the following files can be found: C:/tools/cuda\lib/x64/cudnn.lib C:/tools/cuda\lib/x64/cudnn.lib --- tensorflow/tools/ci_build/windows/bazel/bazel_test_lib.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/windows/bazel/bazel_test_lib.sh b/tensorflow/tools/ci_build/windows/bazel/bazel_test_lib.sh index 7b2d7e1a56..d654b433e7 100644 --- a/tensorflow/tools/ci_build/windows/bazel/bazel_test_lib.sh +++ b/tensorflow/tools/ci_build/windows/bazel/bazel_test_lib.sh @@ -120,7 +120,9 @@ function run_configure_for_gpu_build { export TF_CUDA_VERSION=9.0 export CUDA_TOOLKIT_PATH="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v9.0" export TF_CUDNN_VERSION=7.0 - export CUDNN_INSTALL_PATH="C:/tools/cuda" + if [ -z "$CUDNN_INSTALL_PATH" ]; then + export CUDNN_INSTALL_PATH="C:/tools/cuda" + fi export TF_CUDA_COMPUTE_CAPABILITIES="3.7" if [ -z "$TF_ENABLE_XLA" ]; then export TF_ENABLE_XLA=0 -- GitLab From aec2496567a7bfd508fc487dec474263b6a7481f Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Tue, 27 Mar 2018 11:54:26 -0700 Subject: [PATCH 273/906] Exclude Python C extension from tensorflow/c:srcs target. The Python extensions aren't part of the official C API. PiperOrigin-RevId: 190649576 --- tensorflow/c/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index 426f97b844..7f03e40d38 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -34,6 +34,8 @@ filegroup( exclude = [ "c_api_experimental.cc", "c_api_experimental.h", + "python_api.cc", + "python_api.h", "*test*", ], ), -- GitLab From fd77211de17bf053cc8f5a82c8eff1818451120c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 12:00:44 -0700 Subject: [PATCH 274/906] Replaced calls to deprecated tensorflow::StringPiece methods with their tensorflow::str_util equivalents. This will allow the deprecated methods to be removed. PiperOrigin-RevId: 190650553 --- tensorflow/cc/framework/cc_op_gen_test.cc | 5 +- tensorflow/cc/framework/scope.cc | 3 +- tensorflow/compiler/aot/codegen_test.cc | 3 +- tensorflow/compiler/aot/tfcompile_main.cc | 2 +- .../compiler/xla/service/hlo_graph_dumper.cc | 6 +- .../compiler/xla/service/user_computation.cc | 4 +- .../xla/tests/compute_constant_test.cc | 9 +-- .../xla/tests/xla_hlo_profile_test.cc | 4 +- tensorflow/contrib/cloud/kernels/BUILD | 1 + .../kernels/bigquery_table_accessor_test.cc | 5 +- .../session_bundle/session_bundle_test.cc | 30 ++++---- .../contrib/session_bundle/signature_test.cc | 68 ++++++++++--------- tensorflow/core/grappler/costs/BUILD | 1 + .../core/grappler/costs/graph_properties.cc | 4 +- tensorflow/core/lib/io/inputbuffer_test.cc | 3 +- tensorflow/core/lib/io/recordio_test.cc | 3 +- tensorflow/python/framework/python_op_gen.cc | 2 +- .../python/framework/python_op_gen_main.cc | 4 +- tensorflow/stream_executor/kernel.cc | 3 +- tensorflow/stream_executor/lib/str_util.h | 2 +- 20 files changed, 92 insertions(+), 70 deletions(-) diff --git a/tensorflow/cc/framework/cc_op_gen_test.cc b/tensorflow/cc/framework/cc_op_gen_test.cc index 1e0f2d241b..5d9dfd95a5 100644 --- a/tensorflow/cc/framework/cc_op_gen_test.cc +++ b/tensorflow/cc/framework/cc_op_gen_test.cc @@ -19,6 +19,7 @@ limitations under the License. #include "tensorflow/core/framework/op_gen_lib.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/io/path.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { @@ -61,12 +62,12 @@ op { )"; void ExpectHasSubstr(StringPiece s, StringPiece expected) { - EXPECT_TRUE(s.contains(expected)) + EXPECT_TRUE(str_util::StrContains(s, expected)) << "'" << s << "' does not contain '" << expected << "'"; } void ExpectDoesNotHaveSubstr(StringPiece s, StringPiece expected) { - EXPECT_FALSE(s.contains(expected)) + EXPECT_FALSE(str_util::StrContains(s, expected)) << "'" << s << "' contains '" << expected << "'"; } diff --git a/tensorflow/cc/framework/scope.cc b/tensorflow/cc/framework/scope.cc index 7164249262..c143b97833 100644 --- a/tensorflow/cc/framework/scope.cc +++ b/tensorflow/cc/framework/scope.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/graph/node_builder.h" +#include "tensorflow/core/lib/strings/str_util.h" namespace tensorflow { @@ -218,7 +219,7 @@ std::unordered_set Scope::Impl::GetColocationConstraints( if (GetNodeAttr(attrs, kColocationAttrName, &node_constraints).ok()) { for (const string& entry : node_constraints) { StringPiece s(entry); - if (s.Consume(kColocationGroupPrefix)) { + if (str_util::ConsumePrefix(&s, kColocationGroupPrefix)) { current_constraints.insert(s.ToString()); } } diff --git a/tensorflow/compiler/aot/codegen_test.cc b/tensorflow/compiler/aot/codegen_test.cc index 972b7d51ec..2642536c4f 100644 --- a/tensorflow/compiler/aot/codegen_test.cc +++ b/tensorflow/compiler/aot/codegen_test.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/core/stringpiece.h" #include "tensorflow/core/lib/io/path.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/test.h" @@ -33,7 +34,7 @@ namespace { void ExpectErrorContains(const Status& status, StringPiece str) { EXPECT_NE(Status::OK(), status); - EXPECT_TRUE(StringPiece(status.error_message()).contains(str)) + EXPECT_TRUE(str_util::StrContains(status.error_message(), str)) << "expected error: " << status.error_message() << " to contain: " << str; } diff --git a/tensorflow/compiler/aot/tfcompile_main.cc b/tensorflow/compiler/aot/tfcompile_main.cc index e2f01179d4..8ea014c2ee 100644 --- a/tensorflow/compiler/aot/tfcompile_main.cc +++ b/tensorflow/compiler/aot/tfcompile_main.cc @@ -55,7 +55,7 @@ const char kUsageHeader[] = "\n"; Status ReadProtoFile(const string& fname, protobuf::Message* proto) { - if (StringPiece(fname).ends_with(".pbtxt")) { + if (str_util::EndsWith(fname, ".pbtxt")) { return ReadTextProto(Env::Default(), fname, proto); } else { return ReadBinaryProto(Env::Default(), fname, proto); diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index 1dc72355cf..25702dc65e 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -823,7 +823,7 @@ string HloDotDumper::GetInstructionNodeInlinedOperands( // Otherwise, print e.g. "%constant.42 (s32[100])". string constant_name; - if (tensorflow::StringPiece(constant->name()).starts_with("constant")) { + if (tensorflow::str_util::StartsWith(constant->name(), "constant")) { constant_name = constant->name(); } else { constant_name = StrCat("constant ", constant->name()); @@ -1041,8 +1041,8 @@ string HloDotDumper::GetInstructionNodeLabel(const HloInstruction* instr) { // The HLO instruction name contains usually the opcode, e.g. "%add.42" is // an add instruction. In this case we render just the name. - if (tensorflow::StringPiece(instr->name()) - .starts_with(HloOpcodeString(instr->opcode()))) { + if (tensorflow::str_util::StartsWith(instr->name(), + HloOpcodeString(instr->opcode()))) { return Printf("%s", HtmlLikeStringSanitize(instr->name())); } string extended_opcode = diff --git a/tensorflow/compiler/xla/service/user_computation.cc b/tensorflow/compiler/xla/service/user_computation.cc index 0dca30a804..fcdb2e01fb 100644 --- a/tensorflow/compiler/xla/service/user_computation.cc +++ b/tensorflow/compiler/xla/service/user_computation.cc @@ -1284,8 +1284,8 @@ StatusOr UserComputation::AddCustomCallInstruction( TF_RETURN_IF_ERROR(LookUpRequest(handle).status()); } - if (tensorflow::StringPiece(custom_call_request.call_target_name()) - .starts_with("$")) { + if (tensorflow::str_util::StartsWith(custom_call_request.call_target_name(), + "$")) { return InvalidArgument( "Invalid custom_call_target \"%s\": Call targets that start with '$' " "are reserved for internal use.", diff --git a/tensorflow/compiler/xla/tests/compute_constant_test.cc b/tensorflow/compiler/xla/tests/compute_constant_test.cc index ec2c580670..e5a03b49ad 100644 --- a/tensorflow/compiler/xla/tests/compute_constant_test.cc +++ b/tensorflow/compiler/xla/tests/compute_constant_test.cc @@ -31,6 +31,7 @@ limitations under the License. #include "tensorflow/compiler/xla/tests/test_macros.h" #include "tensorflow/compiler/xla/tests/test_utils.h" #include "tensorflow/compiler/xla/xla_data.pb.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/types.h" namespace xla { @@ -167,8 +168,8 @@ TEST_F(ComputeConstantTest, DirectParamMissing) { EXPECT_FALSE(IsConstant(computation, &b)); auto value = ComputeConstantScalar(client, computation, &b); - EXPECT_TRUE(tensorflow::StringPiece(value.status().ToString()) - .contains("depends on a parameter")) + EXPECT_TRUE(tensorflow::str_util::StrContains(value.status().ToString(), + "depends on a parameter")) << value.status(); } } @@ -183,8 +184,8 @@ TEST_F(ComputeConstantTest, IndirectParamMissing) { EXPECT_FALSE(IsConstant(computation, &b)); auto value = ComputeConstantScalar(client, computation, &b); - EXPECT_TRUE(tensorflow::StringPiece(value.status().ToString()) - .contains("depends on a parameter")) + EXPECT_TRUE(tensorflow::str_util::StrContains(value.status().ToString(), + "depends on a parameter")) << value.status(); } } diff --git a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc index 24b9f37a80..ff3418a128 100644 --- a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc +++ b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc @@ -27,6 +27,7 @@ limitations under the License. #include "tensorflow/compiler/xla/tests/test_utils.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/gtl/flatmap.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/types.h" @@ -294,7 +295,8 @@ XLA_TEST_F(HloProfileTest, auto while_body_profile_start = std::find_if(profile_output_lines.begin(), profile_output_lines.end(), [](tensorflow::StringPiece s) { - return s.starts_with("Execution profile for body"); + return tensorflow::str_util::StartsWith( + s, "Execution profile for body"); }); ASSERT_NE(while_body_profile_start, profile_output_lines.end()); diff --git a/tensorflow/contrib/cloud/kernels/BUILD b/tensorflow/contrib/cloud/kernels/BUILD index 56f930a9a8..d5fc604de9 100644 --- a/tensorflow/contrib/cloud/kernels/BUILD +++ b/tensorflow/contrib/cloud/kernels/BUILD @@ -73,6 +73,7 @@ tf_cc_test( ], deps = [ ":bigquery_table_accessor", + "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", diff --git a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor_test.cc b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor_test.cc index e9b79a066d..7416eb19d3 100644 --- a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor_test.cc +++ b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor_test.cc @@ -18,6 +18,7 @@ limitations under the License. #include "tensorflow/core/example/feature.pb.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/gtl/stl_util.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/cloud/http_request_fake.h" #include "tensorflow/core/platform/test.h" @@ -28,8 +29,8 @@ constexpr char kTestProject[] = "test-project"; constexpr char kTestDataset[] = "test-dataset"; constexpr char kTestTable[] = "test-table"; -bool HasSubstr(const string& base, const string& substr) { - bool ok = StringPiece(base).contains(substr); +bool HasSubstr(StringPiece base, StringPiece substr) { + bool ok = str_util::StrContains(base, substr); EXPECT_TRUE(ok) << base << ", expected substring " << substr; return ok; } diff --git a/tensorflow/contrib/session_bundle/session_bundle_test.cc b/tensorflow/contrib/session_bundle/session_bundle_test.cc index 6d997bac9e..612623ae30 100644 --- a/tensorflow/contrib/session_bundle/session_bundle_test.cc +++ b/tensorflow/contrib/session_bundle/session_bundle_test.cc @@ -30,6 +30,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/io/path.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/public/session.h" @@ -239,8 +240,8 @@ TEST(LoadSessionBundleFromPath, BasicTestRunOptionsThreadPoolInvalid) { // Expect failed session run calls with invalid run-options. EXPECT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Invalid inter_op_thread_pool: 2")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Invalid inter_op_thread_pool: 2")) << status.error_message(); } @@ -314,8 +315,8 @@ TEST_F(SessionBundleTest, ServingGraphEmpty) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE(StringPiece(status_.error_message()) - .contains("Expected exactly one serving GraphDef")) + EXPECT_TRUE(str_util::StrContains(status_.error_message(), + "Expected exactly one serving GraphDef")) << status_.error_message(); } @@ -330,8 +331,9 @@ TEST_F(SessionBundleTest, ServingGraphAnyIncorrectType) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE(StringPiece(status_.error_message()) - .contains("Expected Any type_url for: tensorflow.GraphDef")) + EXPECT_TRUE( + str_util::StrContains(status_.error_message(), + "Expected Any type_url for: tensorflow.GraphDef")) << status_.error_message(); } @@ -347,7 +349,8 @@ TEST_F(SessionBundleTest, ServingGraphAnyValueCorrupted) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE(StringPiece(status_.error_message()).contains("Failed to unpack")) + EXPECT_TRUE( + str_util::StrContains(status_.error_message(), "Failed to unpack")) << status_.error_message(); } @@ -362,9 +365,9 @@ TEST_F(SessionBundleTest, AssetFileAnyIncorrectType) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE( - StringPiece(status_.error_message()) - .contains("Expected Any type_url for: tensorflow.serving.AssetFile")) + EXPECT_TRUE(str_util::StrContains( + status_.error_message(), + "Expected Any type_url for: tensorflow.serving.AssetFile")) << status_.error_message(); } @@ -380,7 +383,8 @@ TEST_F(SessionBundleTest, AssetFileAnyValueCorrupted) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE(StringPiece(status_.error_message()).contains("Failed to unpack")) + EXPECT_TRUE( + str_util::StrContains(status_.error_message(), "Failed to unpack")) << status_.error_message(); } @@ -395,8 +399,8 @@ TEST_F(SessionBundleTest, InitOpTooManyValues) { }); status_ = LoadSessionBundleFromPath(options_, path, &bundle_); EXPECT_FALSE(status_.ok()); - EXPECT_TRUE(StringPiece(status_.error_message()) - .contains("Expected exactly one serving init op")) + EXPECT_TRUE(str_util::StrContains(status_.error_message(), + "Expected exactly one serving init op")) << status_.error_message(); } diff --git a/tensorflow/contrib/session_bundle/signature_test.cc b/tensorflow/contrib/session_bundle/signature_test.cc index 741b7fde9b..b1ff55552e 100644 --- a/tensorflow/contrib/session_bundle/signature_test.cc +++ b/tensorflow/contrib/session_bundle/signature_test.cc @@ -26,6 +26,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/core/stringpiece.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/public/session.h" @@ -33,8 +34,8 @@ namespace tensorflow { namespace serving { namespace { -static bool HasSubstr(const string& base, const string& substr) { - bool ok = StringPiece(base).contains(substr); +static bool HasSubstr(StringPiece base, StringPiece substr) { + bool ok = str_util::StrContains(base, substr); EXPECT_TRUE(ok) << base << ", expected substring " << substr; return ok; } @@ -69,8 +70,8 @@ TEST(GetClassificationSignature, MissingSignature) { ClassificationSignature signature; const Status status = GetClassificationSignature(meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected a classification signature")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected a classification signature")) << status.error_message(); } @@ -86,8 +87,8 @@ TEST(GetClassificationSignature, WrongSignatureType) { ClassificationSignature signature; const Status status = GetClassificationSignature(meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected a classification signature")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected a classification signature")) << status.error_message(); } @@ -122,8 +123,8 @@ TEST(GetNamedClassificationSignature, MissingSignature) { const Status status = GetNamedClassificationSignature("foo", meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Missing signature named \"foo\"")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Missing signature named \"foo\"")) << status.error_message(); } @@ -141,9 +142,9 @@ TEST(GetNamedClassificationSignature, WrongSignatureType) { const Status status = GetNamedClassificationSignature("foo", meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE( - StringPiece(status.error_message()) - .contains("Expected a classification signature for name \"foo\"")) + EXPECT_TRUE(str_util::StrContains( + status.error_message(), + "Expected a classification signature for name \"foo\"")) << status.error_message(); } @@ -176,8 +177,8 @@ TEST(GetRegressionSignature, MissingSignature) { RegressionSignature signature; const Status status = GetRegressionSignature(meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected a regression signature")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected a regression signature")) << status.error_message(); } @@ -193,8 +194,8 @@ TEST(GetRegressionSignature, WrongSignatureType) { RegressionSignature signature; const Status status = GetRegressionSignature(meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected a regression signature")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected a regression signature")) << status.error_message(); } @@ -227,8 +228,8 @@ TEST(GetNamedSignature, MissingSignature) { Signature signature; const Status status = GetNamedSignature("foo", meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Missing signature named \"foo\"")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Missing signature named \"foo\"")) << status.error_message(); } @@ -370,7 +371,7 @@ TEST(RunClassification, RunNotOk) { const Status status = RunClassification(signature, input_tensor, &session, &classes_tensor, nullptr); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()).contains("Data is gone")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), "Data is gone")) << status.error_message(); } @@ -386,7 +387,8 @@ TEST(RunClassification, TooManyOutputs) { const Status status = RunClassification(signature, input_tensor, &session, &classes_tensor, nullptr); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()).contains("Expected 1 output")) + EXPECT_TRUE( + str_util::StrContains(status.error_message(), "Expected 1 output")) << status.error_message(); } @@ -402,8 +404,9 @@ TEST(RunClassification, WrongBatchOutputs) { const Status status = RunClassification(signature, input_tensor, &session, &classes_tensor, nullptr); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Input batch size did not match output batch size")) + EXPECT_TRUE( + str_util::StrContains(status.error_message(), + "Input batch size did not match output batch size")) << status.error_message(); } @@ -449,7 +452,7 @@ TEST_F(RunRegressionTest, RunNotOk) { const Status status = RunRegression(signature_, input_tensor_, &session_, &output_tensor_); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()).contains("Data is gone")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), "Data is gone")) << status.error_message(); } @@ -460,8 +463,9 @@ TEST_F(RunRegressionTest, MismatchedSizeForBatchInputAndOutput) { const Status status = RunRegression(signature_, input_tensor_, &session_, &output_tensor_); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Input batch size did not match output batch size")) + EXPECT_TRUE( + str_util::StrContains(status.error_message(), + "Input batch size did not match output batch size")) << status.error_message(); } @@ -488,7 +492,7 @@ TEST(GetSignatures, MissingSignature) { const auto status = GetSignatures(meta_graph_def, &read_signatures); EXPECT_EQ(tensorflow::error::FAILED_PRECONDITION, status.code()); EXPECT_TRUE( - StringPiece(status.error_message()).contains("Expected exactly one")) + str_util::StrContains(status.error_message(), "Expected exactly one")) << status.error_message(); } @@ -502,9 +506,9 @@ TEST(GetSignatures, WrongProtoInAny) { Signatures read_signatures; const auto status = GetSignatures(meta_graph_def, &read_signatures); EXPECT_EQ(tensorflow::error::FAILED_PRECONDITION, status.code()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected Any type_url for: " - "tensorflow.serving.Signatures")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected Any type_url for: " + "tensorflow.serving.Signatures")) << status.error_message(); } @@ -519,7 +523,7 @@ TEST(GetSignatures, JunkInAny) { Signatures read_signatures; const auto status = GetSignatures(meta_graph_def, &read_signatures); EXPECT_EQ(tensorflow::error::FAILED_PRECONDITION, status.code()); - EXPECT_TRUE(StringPiece(status.error_message()).contains("Failed to unpack")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), "Failed to unpack")) << status.error_message(); } @@ -567,7 +571,7 @@ TEST(GetSignatures, MultipleSignaturesNotOK) { const auto status = GetSignatures(meta_graph_def, &read_signatures); EXPECT_EQ(tensorflow::error::FAILED_PRECONDITION, status.code()); EXPECT_TRUE( - StringPiece(status.error_message()).contains("Expected exactly one")) + str_util::StrContains(status.error_message(), "Expected exactly one")) << status.error_message(); } @@ -641,8 +645,8 @@ TEST(GetGenericSignature, WrongSignatureType) { const Status status = GetGenericSignature("generic_bindings", meta_graph_def, &signature); ASSERT_FALSE(status.ok()); - EXPECT_TRUE(StringPiece(status.error_message()) - .contains("Expected a generic signature:")) + EXPECT_TRUE(str_util::StrContains(status.error_message(), + "Expected a generic signature:")) << status.error_message(); } diff --git a/tensorflow/core/grappler/costs/BUILD b/tensorflow/core/grappler/costs/BUILD index 5336df1f51..df5a26f475 100644 --- a/tensorflow/core/grappler/costs/BUILD +++ b/tensorflow/core/grappler/costs/BUILD @@ -55,6 +55,7 @@ cc_library( ":utils", "//tensorflow/core:core_cpu_base", "//tensorflow/core:framework", + "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:utils", diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index 817247e379..a5fd79447d 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -23,6 +23,7 @@ limitations under the License. #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/grappler/costs/utils.h" #include "tensorflow/core/grappler/utils.h" +#include "tensorflow/core/lib/strings/str_util.h" namespace tensorflow { namespace grappler { @@ -251,8 +252,7 @@ typename DisjointSet::Rep* DisjointSet::Find(Handle value) { } bool IsQueue(const Node& node) { - StringPiece type(node.type_string()); - return type.ends_with("QueueV2"); + return str_util::EndsWith(node.type_string(), "QueueV2"); } // Returns true if the node is an Enter op AND its input is a Queue. diff --git a/tensorflow/core/lib/io/inputbuffer_test.cc b/tensorflow/core/lib/io/inputbuffer_test.cc index 6be1f819c2..3608008b30 100644 --- a/tensorflow/core/lib/io/inputbuffer_test.cc +++ b/tensorflow/core/lib/io/inputbuffer_test.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/test.h" @@ -287,7 +288,7 @@ TEST(InputBuffer, Seek) { EXPECT_TRUE(errors::IsOutOfRange(in.ReadNBytes(1, &read))); EXPECT_TRUE( - StringPiece(in.Seek(-1).ToString()).contains("negative position")); + str_util::StrContains(in.Seek(-1).ToString(), "negative position")); } } diff --git a/tensorflow/core/lib/io/recordio_test.cc b/tensorflow/core/lib/io/recordio_test.cc index b7e51256a2..63235761d9 100644 --- a/tensorflow/core/lib/io/recordio_test.cc +++ b/tensorflow/core/lib/io/recordio_test.cc @@ -20,6 +20,7 @@ limitations under the License. #include "tensorflow/core/lib/io/record_reader.h" #include "tensorflow/core/lib/io/record_writer.h" #include "tensorflow/core/lib/random/simple_philox.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/test.h" @@ -218,7 +219,7 @@ TEST_F(RecordioTest, RandomRead) { // Tests of all the error paths in log_reader.cc follow: static void AssertHasSubstr(StringPiece s, StringPiece expected) { - EXPECT_TRUE(StringPiece(s).contains(expected)) + EXPECT_TRUE(str_util::StrContains(s, expected)) << s << " does not contain " << expected; } diff --git a/tensorflow/python/framework/python_op_gen.cc b/tensorflow/python/framework/python_op_gen.cc index 9850f0becc..e5e3b82199 100644 --- a/tensorflow/python/framework/python_op_gen.cc +++ b/tensorflow/python/framework/python_op_gen.cc @@ -448,7 +448,7 @@ string AttrValueToPython(const string& type, const AttrValue& value, return TensorToPython(value.tensor()); } else if (type == "func") { return StringToPython(value.func().name()); - } else if (StringPiece(type).starts_with("list(")) { + } else if (str_util::StartsWith(type, "list(")) { return strings::StrCat("[", AttrListToPython(value, dtype_module), "]"); } else { return "?"; diff --git a/tensorflow/python/framework/python_op_gen_main.cc b/tensorflow/python/framework/python_op_gen_main.cc index bc5ca195da..ca6ed42bee 100644 --- a/tensorflow/python/framework/python_op_gen_main.cc +++ b/tensorflow/python/framework/python_op_gen_main.cc @@ -27,6 +27,7 @@ limitations under the License. #include "tensorflow/core/lib/io/inputbuffer.h" #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/strings/scanner.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/init_main.h" #include "tensorflow/core/platform/logging.h" @@ -95,7 +96,8 @@ string InferSourceFileName(const char* argv_zero) { // operators defined in _ops.cc const char* kExecPrefix = "gen_"; const char* kExecSuffix = "_py_wrappers_cc"; - if (command_str.Consume(kExecPrefix) && command_str.ends_with(kExecSuffix)) { + if (str_util::ConsumePrefix(&command_str, kExecPrefix) && + str_util::EndsWith(command_str, kExecSuffix)) { command_str.remove_suffix(strlen(kExecSuffix)); return strings::StrCat(command_str, ".cc"); } else { diff --git a/tensorflow/stream_executor/kernel.cc b/tensorflow/stream_executor/kernel.cc index 81e531efb3..636199cfa2 100644 --- a/tensorflow/stream_executor/kernel.cc +++ b/tensorflow/stream_executor/kernel.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/stream_executor/platform/port.h" +#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/stream_executor/lib/demangle.h" #include "tensorflow/stream_executor/platform.h" #include "tensorflow/stream_executor/platform/logging.h" @@ -96,7 +97,7 @@ static const char *kStubPrefix = "__device_stub_"; void KernelBase::set_name(port::StringPiece name) { name_ = name.ToString(); port::StringPiece stubless_name = name; - if (name.starts_with(kStubPrefix)) { + if (tensorflow::str_util::StartsWith(name, kStubPrefix)) { stubless_name.remove_prefix(strlen(kStubPrefix)); } demangled_name_ = port::Demangle(stubless_name.data()); diff --git a/tensorflow/stream_executor/lib/str_util.h b/tensorflow/stream_executor/lib/str_util.h index 4dd6f3b0cc..5dd3d06aff 100644 --- a/tensorflow/stream_executor/lib/str_util.h +++ b/tensorflow/stream_executor/lib/str_util.h @@ -29,7 +29,7 @@ using tensorflow::str_util::Split; // Returns a copy of the input string 'str' with the given 'suffix' // removed. If the suffix doesn't match, returns a copy of the original string. inline string StripSuffixString(port::StringPiece str, port::StringPiece suffix) { - if (str.ends_with(suffix)) { + if (tensorflow::str_util::EndsWith(str, suffix)) { str.remove_suffix(suffix.size()); } return str.ToString(); -- GitLab From 7c06ae2fd9b933e83aea0e5088c0b32b7c1fcaaf Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 27 Mar 2018 12:06:19 -0700 Subject: [PATCH 275/906] Remove warnings for initialize_variables (#18023) The `initialize_variables` has been deprecated and replaced with `tf.variables_initializer`. This fix makes the change and fixes the following warning in array_ops_test.py: ``` WARNING:tensorflow:From /private/var/tmp/_bazel_ytang/48f7de64c479bcefe5e55c65866b55a6/execroot/org_tensorflow/bazel-out/darwin-opt/bin/tensorflow/python/kernel_tests/array_ops_test.runfiles/org_tensorflow/tensorflow/python/util/tf_should_use.py:118: initialize_variables (from tensorflow.python.ops.variables) is deprecated and will be removed after 2017-03-02. Instructions for updating: Use `tf.variables_initializer` instead. ``` Signed-off-by: Yong Tang --- tensorflow/python/kernel_tests/array_ops_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index d0ba8020c1..b82aa47ebe 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -890,7 +890,7 @@ class StridedSliceAssignChecker(object): var = resource_variable_ops.ResourceVariable(self.x) else: var = variables.Variable(self.x) - sess.run(variables.initialize_variables([var])) + sess.run(variables.variables_initializer([var])) val = sess.run(var[index].assign(value)) # val_copy is used to check that tf.assign works equivalently to the # assign method above. -- GitLab From 4a24413ee92c23727e11108bfd9b823ac09ef209 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 27 Mar 2018 12:11:39 -0700 Subject: [PATCH 276/906] Validate axis in shape function of tf.reverse (#18024) * Validate axis in shape function of tf.reverse tf.reverse requires the axis to be in the range of `[-rank(tensor), rank(tensor))`. Previously the validation is only done in runtime though it is possible to validate axis inside the shape function if the shape of the input tensor is already known. This fix add the validation in the shape function. Signed-off-by: Yong Tang * Replace with temp variable Signed-off-by: Yong Tang * Sanitize with clang-foramt -i Signed-off-by: Yong Tang * Validate multiple specification of axis in tf.reverse as well Signed-off-by: Yong Tang * Add test case for axis validation in shape function for tf.reverse Signed-off-by: Yong Tang * Update existing test cases Signed-off-by: Yong Tang --- tensorflow/core/ops/array_ops.cc | 26 ++++++++++++++++++- .../python/kernel_tests/array_ops_test.py | 24 ++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/ops/array_ops.cc b/tensorflow/core/ops/array_ops.cc index 39b92464cb..f97f1645a6 100644 --- a/tensorflow/core/ops/array_ops.cc +++ b/tensorflow/core/ops/array_ops.cc @@ -752,11 +752,35 @@ REGISTER_OP("ReverseV2") ShapeHandle input = c->input(0); ShapeHandle axis; TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &axis)); - // TODO(aselle): if input(0)'s dimension is known we could validate axis if (c->Rank(input) > 8) { return errors::InvalidArgument( "reverse does not work on tensors with more than 8 dimensions"); } + const Tensor* axis_tensor = c->input_tensor(1); + if (axis_tensor != nullptr && c->RankKnown(input)) { + int32 rank = c->Rank(input); + std::vector axis_value; + if (axis_tensor->dtype() == DT_INT32) { + axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); + } else { + axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); + } + std::vector axes_dense(c->Rank(input), false); + for (int i = 0; i < axis_value.size(); i++) { + int64 canonical_axis = + axis_value[i] < 0 ? rank + axis_value[i] : axis_value[i]; + if (canonical_axis < 0 || canonical_axis >= rank) { + return errors::InvalidArgument("'axis'[", i, "] = ", axis_value[i], + " is out of valid range [", 0, ", ", + rank - 1); + } + if (axes_dense[canonical_axis]) { + return errors::InvalidArgument("axis ", canonical_axis, + " specified more than once."); + } + axes_dense[canonical_axis] = true; + } + } c->set_output(0, input); return Status::OK(); }); diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index b82aa47ebe..64c1760d5e 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -315,21 +315,39 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf_4, np.asarray(x_np)[:, ::-1]) self.assertAllEqual(x_tf_5, np.asarray(x_np)[::-1, ::-1]) + # This test covers the axis validation in the shape function + # (no eval()) + def testInvalidAxis(self): + x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [-30]) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [2]) + with self.assertRaisesRegexp(ValueError, + "axis 0 specified more than once"): + array_ops.reverse_v2(x_np, [0, -2]) + # This is the version of reverse that uses axis indices rather than # bool tensors # TODO(b/32254538): Change this test to use array_ops.reverse + # + # Note: this test passes placeholder as constant axis is validated + # in shape function (see testInvalidAxis) def testInvalid(self): x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + axis = array_ops.placeholder(dtypes.int32) with self.test_session(): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [-30]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [-30]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [2]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "axis 0 specified more than once"): - array_ops.reverse_v2(x_np, [0, -2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [0, -2]}) def testReverse1DimAuto(self): for dtype in [ -- GitLab From 5da1cdcf0032f63c22afb41a460fd44c52ada048 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Tue, 27 Mar 2018 12:09:59 -0700 Subject: [PATCH 277/906] Improved shape inference for reshape PiperOrigin-RevId: 190651873 --- .../python/kernel_tests/shape_ops_test.py | 5 +- .../contrib/signal/python/ops/shape_ops.py | 2 + tensorflow/core/ops/array_ops.cc | 104 ++++++++++++------ tensorflow/core/ops/array_ops_test.cc | 6 +- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py index 1c052354b8..bc4663fbb0 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py @@ -338,7 +338,10 @@ class FrameTest(test.TestCase): def test_constant_folding(self): """frame should be constant foldable for constant inputs.""" - for pad_end in [False, True]: + # Padding is incorrectly defined in shape_ops.py (the rank of the padding + # tensor should be equal to the rank of the input tensor + 1): only test + # with padding set to False to avoid this. + for pad_end in [False]: g = ops.Graph() with g.as_default(): frame_length, frame_step = 32, 16 diff --git a/tensorflow/contrib/signal/python/ops/shape_ops.py b/tensorflow/contrib/signal/python/ops/shape_ops.py index 1ddc2941ec..97fe20866b 100644 --- a/tensorflow/contrib/signal/python/ops/shape_ops.py +++ b/tensorflow/contrib/signal/python/ops/shape_ops.py @@ -139,6 +139,8 @@ def frame(signal, frame_length, frame_step, pad_end=False, pad_value=0, axis=-1, [[0, pad_samples]], array_ops.zeros([num_inner_dimensions, 2], dtype=pad_samples.dtype)], 0) + # TODO(rjryan): the paddings tensor must of rank tf.rank(signal) + 1. This + # isn't the case here and should be fixed. signal = array_ops.pad(signal, paddings, constant_values=pad_value) signal_shape = array_ops.shape(signal) diff --git a/tensorflow/core/ops/array_ops.cc b/tensorflow/core/ops/array_ops.cc index 39b92464cb..88d2aa3f41 100644 --- a/tensorflow/core/ops/array_ops.cc +++ b/tensorflow/core/ops/array_ops.cc @@ -178,46 +178,88 @@ Status SetOutputShapeForReshape(InferenceContext* c) { c->set_output(0, out); return Status::OK(); } - DimensionHandle num_in_elems = c->NumElements(in); - if (c->FullyDefined(out)) { - DimensionHandle num_out_elems = c->NumElements(out); - if (c->ValueKnown(num_in_elems) && - c->Value(num_in_elems) != c->Value(num_out_elems)) { - return errors::InvalidArgument( - "Cannot reshape a tensor with ", c->DebugString(num_in_elems), - " elements to shape ", c->DebugString(out), " (", - c->DebugString(num_out_elems), " elements)"); - } - c->set_output(0, out); - return Status::OK(); - } - if (c->ValueKnown(num_in_elems)) { + if (c->RankKnown(out) && c->RankKnown(in)) { // We don't know the number of output elements, but we can try to infer // the missing dimension. - int32 unknown_idx = -1; bool too_many_unknown = false; - DimensionHandle known_elems = c->MakeDim(1); - for (int32 i = 0; i < c->Rank(out); ++i) { - DimensionHandle dim = c->Dim(out, i); - if (!c->ValueKnown(dim)) { - if (unknown_idx >= 0) { - too_many_unknown = true; - break; + int32 out_unknown_idx = -1; + + DimensionHandle known_out_elems = c->NumElements(out); + if (!c->ValueKnown(known_out_elems)) { + known_out_elems = c->MakeDim(1); + for (int32 i = 0; i < c->Rank(out); ++i) { + DimensionHandle dim = c->Dim(out, i); + if (!c->ValueKnown(dim)) { + if (out_unknown_idx >= 0) { + too_many_unknown = true; + break; + } + out_unknown_idx = i; + } else { + TF_RETURN_IF_ERROR( + c->Multiply(known_out_elems, dim, &known_out_elems)); } - unknown_idx = i; - } else { - TF_RETURN_IF_ERROR(c->Multiply(known_elems, dim, &known_elems)); } } - if (!too_many_unknown && c->Value(known_elems) != 0) { - DimensionHandle inferred_dim; - TF_RETURN_IF_ERROR(c->Divide(num_in_elems, c->Value(known_elems), - true /* evenly_divisible */, &inferred_dim)); - TF_RETURN_IF_ERROR(c->ReplaceDim(out, unknown_idx, inferred_dim, &out)); + int32 in_unknown_idx = -1; + DimensionHandle known_in_elems = c->NumElements(in); + if (!c->ValueKnown(known_in_elems)) { + known_in_elems = c->MakeDim(1); + for (int32 i = 0; i < c->Rank(in); ++i) { + DimensionHandle dim = c->Dim(in, i); + if (!c->ValueKnown(dim)) { + if (in_unknown_idx >= 0) { + too_many_unknown = true; + break; + } + in_unknown_idx = i; + } else { + TF_RETURN_IF_ERROR(c->Multiply(known_in_elems, dim, &known_in_elems)); + } + } } - } + if (!too_many_unknown) { + if (in_unknown_idx < 0 && out_unknown_idx < 0) { + // Just check that the dimensions match. + if (c->Value(known_in_elems) != c->Value(known_out_elems)) { + return errors::InvalidArgument( + "Cannot reshape a tensor with ", c->DebugString(known_in_elems), + " elements to shape ", c->DebugString(out), " (", + c->DebugString(known_out_elems), " elements)"); + } + } else if (in_unknown_idx < 0 && out_unknown_idx >= 0 && + c->Value(known_out_elems) > 0) { + // Input fully known, infer the one missing output dim + DimensionHandle inferred_dim; + TF_RETURN_IF_ERROR(c->Divide(known_in_elems, c->Value(known_out_elems), + true /* evenly_divisible */, + &inferred_dim)); + TF_RETURN_IF_ERROR( + c->ReplaceDim(out, out_unknown_idx, inferred_dim, &out)); + + } else if (in_unknown_idx >= 0 && out_unknown_idx < 0 && + c->Value(known_in_elems) != 0) { + // Output fully known, infer the one missing input dim + DimensionHandle inferred_dim; + TF_RETURN_IF_ERROR(c->Divide(known_out_elems, c->Value(known_in_elems), + true /* evenly_divisible */, + &inferred_dim)); + DimensionHandle unknown_in_dim = c->Dim(in, in_unknown_idx); + TF_RETURN_IF_ERROR( + c->Merge(unknown_in_dim, inferred_dim, &unknown_in_dim)); + } else if (in_unknown_idx >= 0 && out_unknown_idx >= 0) { + // Exactly one unknown dimension in both input and output. These 2 are + // equal iff the known elements are equal. + if (c->Value(known_in_elems) == c->Value(known_out_elems)) { + DimensionHandle unknown_in_dim = c->Dim(in, in_unknown_idx); + TF_RETURN_IF_ERROR( + c->ReplaceDim(out, out_unknown_idx, unknown_in_dim, &out)); + } + } + } + } c->set_output(0, out); return Status::OK(); } diff --git a/tensorflow/core/ops/array_ops_test.cc b/tensorflow/core/ops/array_ops_test.cc index cf5bb5ad84..b1463338fb 100644 --- a/tensorflow/core/ops/array_ops_test.cc +++ b/tensorflow/core/ops/array_ops_test.cc @@ -838,7 +838,7 @@ TEST(ArrayOpsTest, Reshape_ShapeFn) { // Unknown dimensions. // Flatten: new_shape = test::AsTensor({-1}); - INFER_OK(op, "[?];[1]", "[?]"); + INFER_OK(op, "[?];[1]", "[d0_0]"); INFER_OK(op, "[2,2];[1]", "[4]"); // The first dimension is inferred: new_shape = test::AsTensor({2, -1}); @@ -851,6 +851,10 @@ TEST(ArrayOpsTest, Reshape_ShapeFn) { new_shape = test::AsTensor({-1, -1, 2}); INFER_OK(op, "[8];[3]", "[?,?,2]"); + // Symbolic shape propagation + new_shape = test::AsTensor({-1, 2, 3}); + INFER_OK(op, "[?,2,3];[3]", "[d0_0,2,3]"); + // Reshaping to a scalar. new_shape = test::AsTensor({}); INFER_OK(op, "[1];[0]", "[]"); -- GitLab From 3771b5b0d9cbd5a9d34f1d579454b78012cb0bb4 Mon Sep 17 00:00:00 2001 From: Rasmus Munk Larsen Date: Tue, 27 Mar 2018 12:22:49 -0700 Subject: [PATCH 278/906] Update BUILD --- tensorflow/python/kernel_tests/testdata/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/testdata/BUILD b/tensorflow/python/kernel_tests/testdata/BUILD index a4a0dfc139..45264c773a 100644 --- a/tensorflow/python/kernel_tests/testdata/BUILD +++ b/tensorflow/python/kernel_tests/testdata/BUILD @@ -1,7 +1,7 @@ # Data files for kernel tests. package( - default_visibility = ["//tensorflow:internal"], + default_visibility = ["//visibility:public"], ) licenses(["notice"]) # Apache 2.0 -- GitLab From 7fd3ca7ab6e96af7b867c7ae56ac74a3f3393b26 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 27 Mar 2018 12:34:17 -0700 Subject: [PATCH 279/906] Updating test so that it evaluates the optimized and original graph and checks whether the output tensors produced by them are the same. PiperOrigin-RevId: 190655831 --- .../core/grappler/optimizers/constant_folding_test.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index 6340565bcd..dc9c1053d2 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -614,7 +614,8 @@ TEST_F(ConstantFoldingTest, ControlDependencies) { GrapplerItem item; item.fetch.push_back("e"); TF_CHECK_OK(scope.ToGraphDef(&item.graph)); - + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); ConstantFolding optimizer(nullptr /* cpu_device */); GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); @@ -641,6 +642,9 @@ TEST_F(ConstantFoldingTest, ControlDependencies) { } } EXPECT_EQ(1, found); + auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } TEST_F(ConstantFoldingTest, ControlDependenciesEmptyFetch) { -- GitLab From 2700e87f0e5fbc3aa7fe3a6a7ffb7152b894da4a Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Wed, 28 Mar 2018 03:54:20 +0800 Subject: [PATCH 280/906] Fix the incorect rendering of math equation in monte_carlo api guides (#18018) * Fix the math equation in monte_carlo api guides * Replace \( \) with \\( \\) according to guideline --- .../python/contrib.bayesflow.monte_carlo.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md index 956dccb64f..f3db5857ae 100644 --- a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md +++ b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md @@ -6,42 +6,42 @@ Monte Carlo integration and helpers. ## Background Monte Carlo integration refers to the practice of estimating an expectation with -a sample mean. For example, given random variable `Z in R^k` with density `p`, +a sample mean. For example, given random variable `Z in \\(R^k\\)` with density `p`, the expectation of function `f` can be approximated like: ``` -E_p[f(Z)] = \int f(z) p(z) dz - ~ S_n - := n^{-1} \sum_{i=1}^n f(z_i), z_i iid samples from p. +$$E_p[f(Z)] = \int f(z) p(z) dz$$ +$$ ~ S_n + := n^{-1} \sum_{i=1}^n f(z_i), z_i\ iid\ samples\ from\ p.$$ ``` -If `E_p[|f(Z)|] < infinity`, then `S_n --> E_p[f(Z)]` by the strong law of large -numbers. If `E_p[f(Z)^2] < infinity`, then `S_n` is asymptotically normal with -variance `Var[f(Z)] / n`. +If `\\(E_p[|f(Z)|] < infinity\\)`, then `\\(S_n\\) --> \\(E_p[f(Z)]\\)` by the strong law of large +numbers. If `\\(E_p[f(Z)^2] < infinity\\)`, then `\\(S_n\\)` is asymptotically normal with +variance `\\(Var[f(Z)] / n\\)`. Practitioners of Bayesian statistics often find themselves wanting to estimate -`E_p[f(Z)]` when the distribution `p` is known only up to a constant. For +`\\(E_p[f(Z)]\\)` when the distribution `p` is known only up to a constant. For example, the joint distribution `p(z, x)` may be known, but the evidence -`p(x) = \int p(z, x) dz` may be intractable. In that case, a parameterized -distribution family `q_lambda(z)` may be chosen, and the optimal `lambda` is the -one minimizing the KL divergence between `q_lambda(z)` and -`p(z | x)`. We only know `p(z, x)`, but that is sufficient to find `lambda`. +`\\(p(x) = \int p(z, x) dz\\)` may be intractable. In that case, a parameterized +distribution family `\\(q_\lambda(z)\\)` may be chosen, and the optimal `\\(\lambda\\)` is the +one minimizing the KL divergence between `\\(q_\lambda(z)\\)` and +`\\(p(z | x)\\)`. We only know `p(z, x)`, but that is sufficient to find `\\(\lambda\\)`. ## Log-space evaluation and subtracting the maximum Care must be taken when the random variable lives in a high dimensional space. -For example, the naive importance sample estimate `E_q[f(Z) p(Z) / q(Z)]` -involves the ratio of two terms `p(Z) / q(Z)`, each of which must have tails -dropping off faster than `O(|z|^{-(k + 1)})` in order to have finite integral. +For example, the naive importance sample estimate `\\(E_q[f(Z) p(Z) / q(Z)]\\)` +involves the ratio of two terms `\\(p(Z) / q(Z)\\)`, each of which must have tails +dropping off faster than `\\(O(|z|^{-(k + 1)})\\)` in order to have finite integral. This ratio would often be zero or infinity up to numerical precision. For that reason, we write ``` -Log E_q[ f(Z) p(Z) / q(Z) ] - = Log E_q[ exp{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C} ] + C, where -C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ]. +$$Log E_q[ f(Z) p(Z) / q(Z) ]$$ +$$ = Log E_q[ \exp\{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C\} ] + C,$$ where +$$C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ].$$ ``` The maximum value of the exponentiated term will be 0.0, and the expectation -- GitLab From 5d76c7db2ab72f9b0cc70ce12ba0a3395dcc20d3 Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Wed, 28 Mar 2018 03:55:28 +0800 Subject: [PATCH 281/906] Fix minor spelling typos in contrib (#18015) --- .../contrib/bayesflow/python/ops/metropolis_hastings_impl.py | 2 +- .../contrib/estimator/python/estimator/replicate_model_fn.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py b/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py index 05aa134ed5..fdee0a8da6 100644 --- a/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py +++ b/tensorflow/contrib/bayesflow/python/ops/metropolis_hastings_impl.py @@ -238,7 +238,7 @@ def evolve(initial_sample, using the Metropolis-Hastings algorithm. These samples are from a Markov chain whose equilibrium distribution matches the target distribution. - The probability distribution may have an unknown normalization constan. + The probability distribution may have an unknown normalization constant. We parameterize the probability density as follows: ```none diff --git a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py index e0fae2c992..fa2697800e 100644 --- a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py +++ b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py @@ -136,7 +136,7 @@ def replicate_model_fn(model_fn, the train_op argument of `EstimatorSpec`. loss_reduction: controls whether losses are summed or averaged. devices: Optional list of devices to replicate the model across. This - argument can be used to replice only on the subset of available GPUs. + argument can be used to replicate only on the subset of available GPUs. If `None`, then all available GPUs are going to be used for replication. If no GPUs are available, then the model is going to be placed on the CPU. -- GitLab From 0ef36a5de45486ccbc0d6237f86280c2ac22f52e Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 27 Mar 2018 12:56:14 -0700 Subject: [PATCH 282/906] Add broadcast support for softmax_cross_entropy_with_logits (#16784) * Add broadcast support for softmax_cross_entropy_with_logits This fix tries to address the issue raised in 11534 where there was no broadcast support for SoftmaxCrossEntropyWithLogits. This fix adds the broadcast support for SoftmaxCrossEntropyWithLogits, and adds test cases for it. This fix fixes 11534. Signed-off-by: Yong Tang * Add BroadcastBinaryOpOutputShapeFn for shape function This commit adds BroadcastBinaryOpOutputShapeFn, so that the implementation of BroadcastBinaryOpShapeFn coule be reused in SoftmaxCrossEntropyWithLogits. Signed-off-by: Yong Tang * Update the shape function of SoftmaxCrossEntropyWithLogits so that broadcast could be supported. Signed-off-by: Yong Tang * Add broadcast support for SoftmaxCrossEntropyWithLogits Signed-off-by: Yong Tang * Add broadcast support for SoftmaxCrossEntropyWithLogits with GPU Signed-off-by: Yong Tang * Reformat with clang-format Signed-off-by: Yong Tang * Fix shape test issues Signed-off-by: Yong Tang * Remove `_` for gen_nn_ops._softmax_cross_entropy_with_logits as `_` is not needed anymore with the recent changes Signed-off-by: Yong Tang * Sanitize nn_ops.cc with clang-format Signed-off-by: Yong Tang * Add broadcast examples for SoftmaxCrossEntropyWithLogits shape function Signed-off-by: Yong Tang * Add benchmark tests for trival cases Signed-off-by: Yong Tang * Fix pylint issue Signed-off-by: Yong Tang --- tensorflow/core/framework/common_shape_fns.cc | 4 +- tensorflow/core/framework/common_shape_fns.h | 8 +- tensorflow/core/framework/shape_inference.h | 1 + tensorflow/core/kernels/xent_op.cc | 65 ++++++++++----- tensorflow/core/kernels/xent_op.h | 35 +++++--- tensorflow/core/kernels/xent_op_gpu.cu.cc | 9 ++- tensorflow/core/ops/nn_ops.cc | 30 ++++--- tensorflow/core/ops/nn_ops_test.cc | 16 +++- .../python/kernel_tests/xent_op_test.py | 81 ++++++++++++++++++- 9 files changed, 197 insertions(+), 52 deletions(-) diff --git a/tensorflow/core/framework/common_shape_fns.cc b/tensorflow/core/framework/common_shape_fns.cc index 623248b6ce..2fb17c2b02 100644 --- a/tensorflow/core/framework/common_shape_fns.cc +++ b/tensorflow/core/framework/common_shape_fns.cc @@ -1210,7 +1210,7 @@ Status ConcatV2Shape(InferenceContext* c) { c->num_inputs() - 1 /* dim_index */); } -Status BroadcastBinaryOpShapeFn(InferenceContext* c) { +Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index) { ShapeHandle shape_x = c->input(0); ShapeHandle shape_y = c->input(1); if (!c->RankKnown(shape_x) || !c->RankKnown(shape_y)) { @@ -1272,7 +1272,7 @@ Status BroadcastBinaryOpShapeFn(InferenceContext* c) { } } - c->set_output(0, c->MakeShape(dims)); + c->set_output(output_index, c->MakeShape(dims)); return Status::OK(); } diff --git a/tensorflow/core/framework/common_shape_fns.h b/tensorflow/core/framework/common_shape_fns.h index 293c40e04d..7230e0f09c 100644 --- a/tensorflow/core/framework/common_shape_fns.h +++ b/tensorflow/core/framework/common_shape_fns.h @@ -265,9 +265,15 @@ Status ConcatShape(shape_inference::InferenceContext* c, // Shape function for concat operations. Status ConcatV2Shape(shape_inference::InferenceContext* c); +// Shape function for binary operators that broadcast their inputs +// and with output to output_index. +Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index); + // Shape function for binary operators that broadcast their inputs. // Tested by ops/math_ops_test.cc. -Status BroadcastBinaryOpShapeFn(InferenceContext* c); +inline Status BroadcastBinaryOpShapeFn(InferenceContext* c) { + return BroadcastBinaryOpOutputShapeFn(c, 0); +} // Shape function for random operations. Status RandomShape(shape_inference::InferenceContext* c); diff --git a/tensorflow/core/framework/shape_inference.h b/tensorflow/core/framework/shape_inference.h index e3cc848a16..accc587000 100644 --- a/tensorflow/core/framework/shape_inference.h +++ b/tensorflow/core/framework/shape_inference.h @@ -317,6 +317,7 @@ class InferenceContext { input_tensors_as_shapes_ = input_tensors_as_shapes; } + ShapeHandle output(int64 idx) const { return outputs_[idx]; } void set_output(int idx, ShapeHandle shape) { outputs_[idx] = shape; } Status set_output(StringPiece output_name, const std::vector& shapes); diff --git a/tensorflow/core/kernels/xent_op.cc b/tensorflow/core/kernels/xent_op.cc index a6a71fdfaf..ebd19c3d35 100644 --- a/tensorflow/core/kernels/xent_op.cc +++ b/tensorflow/core/kernels/xent_op.cc @@ -17,12 +17,14 @@ limitations under the License. #define EIGEN_USE_THREADS -#include "tensorflow/core/kernels/xent_op.h" #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/kernels/xent_op.h" +#include "tensorflow/core/util/bcast.h" namespace tensorflow { @@ -41,37 +43,56 @@ class SoftmaxXentWithLogitsOp : public OpKernel { void Compute(OpKernelContext* context) override { const Tensor& logits_in = context->input(0); const Tensor& labels_in = context->input(1); - OP_REQUIRES(context, logits_in.IsSameSize(labels_in), - errors::InvalidArgument( - "logits and labels must be same size: logits_size=", - logits_in.shape().DebugString(), - " labels_size=", labels_in.shape().DebugString())); - OP_REQUIRES(context, TensorShapeUtils::IsMatrix(logits_in.shape()), - errors::InvalidArgument("logits must be 2-dimensional")); - // As we already tested that both inputs have the same shape no need to - // check that "labels" is a matrix too. + + TensorShape shape_in = logits_in.shape(); + + BCast bcast(BCast::FromShape(logits_in.shape()), + BCast::FromShape(labels_in.shape())); + if (!logits_in.IsSameSize(labels_in)) { + OP_REQUIRES(context, bcast.IsValid(), + errors::InvalidArgument( + "logits and labels must be broadcastable: logits_size=", + logits_in.shape().DebugString(), " labels_size=", + labels_in.shape().DebugString())); + shape_in = BCast::ToShape(bcast.output_shape()); + } + OP_REQUIRES(context, TensorShapeUtils::IsMatrix(shape_in), + errors::InvalidArgument("logits and labels must be beither " + "2-dimensional, or roadcasted to " + "2-dimensional")); // loss is 1-D (one per example), and size is batch_size. Tensor scratch; OP_REQUIRES_OK( context, context->allocate_temp(DataTypeToEnum::value, - TensorShape({logits_in.dim_size(0), 1}), + TensorShape({shape_in.dim_size(0), 1}), &scratch)); Tensor* loss_out = nullptr; OP_REQUIRES_OK(context, context->allocate_output( - 0, TensorShape({logits_in.dim_size(0)}), &loss_out)); + 0, TensorShape({shape_in.dim_size(0)}), &loss_out)); Tensor* back_out = nullptr; // Try to reuse the logits_in buffer for the backprop output. OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 1, logits_in.shape(), &back_out)); - if (logits_in.dim_size(0) > 0) { + {0}, 1, shape_in, &back_out)); + if (shape_in.dim_size(0) > 0) { functor::XentFunctor functor; - functor(context->eigen_device(), logits_in.matrix(), - labels_in.matrix(), scratch.matrix(), loss_out->vec(), - back_out->matrix()); + if (logits_in.IsSameSize(labels_in)) { + functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), + Eigen::array{1, 1}, + Eigen::array{1, 1}, logits_in.matrix(), + labels_in.matrix(), scratch.matrix(), loss_out->vec(), + back_out->matrix()); + } else { + functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), + BCast::ToIndexArray<2>(bcast.x_bcast()), + BCast::ToIndexArray<2>(bcast.y_bcast()), + logits_in.template shaped(bcast.x_reshape()), + labels_in.template shaped(bcast.y_reshape()), + scratch.matrix(), loss_out->vec(), back_out->matrix()); + } } } }; @@ -81,13 +102,17 @@ class SoftmaxXentWithLogitsOp : public OpKernel { namespace functor { template struct XentFunctorBase { - void operator()(const Device& d, typename TTypes::ConstMatrix logits, + void operator()(const Device& d, + const Eigen::DSizes& shape, + const Eigen::array& logits_bcast, + const Eigen::array& labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, logits, labels, scratch, loss, - backprop); + XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, + logits, labels, scratch, loss, backprop); } }; diff --git a/tensorflow/core/kernels/xent_op.h b/tensorflow/core/kernels/xent_op.h index e689fca7ff..87be17fca9 100644 --- a/tensorflow/core/kernels/xent_op.h +++ b/tensorflow/core/kernels/xent_op.h @@ -18,6 +18,7 @@ limitations under the License. // Functor definition for XentOp, must be compilable by nvcc. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + #include "tensorflow/core/framework/tensor_types.h" namespace tensorflow { @@ -33,7 +34,11 @@ struct XentFunctor { // scratch: temporary tensor, dims: batch_size, 1 // loss: output tensor for the loss, dims: batch_size. // backprop: output tensor for the backprop, dims: batch_size, num_classes. - void operator()(const Device& d, typename TTypes::ConstMatrix logits, + void operator()(const Device &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -45,7 +50,11 @@ struct XentFunctor { // specializations for both device types. template struct XentEigenImpl { - static void Compute(const Device& d, typename TTypes::ConstMatrix logits, + static void Compute(const Device &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -57,8 +66,8 @@ struct XentEigenImpl { const int kBatchDim = 0; const int kClassDim = 1; - const int batch_size = logits.dimension(kBatchDim); - const int num_classes = logits.dimension(kClassDim); + const int batch_size = shape[kBatchDim]; + const int num_classes = shape[kClassDim]; // These arrays are used to reduce along the class dimension, and broadcast // the resulting value to all classes. @@ -84,10 +93,12 @@ struct XentEigenImpl { #endif // max_logits along classes. - scratch.reshape(batch_only).device(d) = logits.maximum(along_class); + scratch.reshape(batch_only).device(d) = + logits.broadcast(logits_bcast).maximum(along_class); // logits - max_logits. - backprop.device(d) = logits - scratch.broadcast(one_by_class); + backprop.device(d) = + logits.broadcast(logits_bcast) - scratch.broadcast(one_by_class); // sum(exp(logits - max_logits)) along classes. scratch.reshape(batch_only).device(d) = backprop.exp().sum(along_class); @@ -99,15 +110,15 @@ struct XentEigenImpl { // sum(-labels * // ((logits - max_logits) - log(sum(exp(logits - max_logits))))) // along classes - loss.device(d) = - (labels * (scratch.log().eval().broadcast(one_by_class) - backprop)) - .eval() - .sum(along_class); + loss.device(d) = (labels.broadcast(labels_bcast) * + (scratch.log().eval().broadcast(one_by_class) - backprop)) + .eval() + .sum(along_class); // backprop: prob - labels, where // prob = exp(logits - max_logits) / sum(exp(logits - max_logits)) - backprop.device(d) = - (backprop.exp() / scratch.broadcast(one_by_class)) - labels; + backprop.device(d) = (backprop.exp() / scratch.broadcast(one_by_class)) - + labels.broadcast(labels_bcast); } }; diff --git a/tensorflow/core/kernels/xent_op_gpu.cu.cc b/tensorflow/core/kernels/xent_op_gpu.cu.cc index 05ee7da490..2c0c0b3a02 100644 --- a/tensorflow/core/kernels/xent_op_gpu.cu.cc +++ b/tensorflow/core/kernels/xent_op_gpu.cu.cc @@ -31,12 +31,17 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { template struct XentFunctor { - void operator()(const GPUDevice& d, typename TTypes::ConstMatrix logits, + void operator()(const GPUDevice &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, logits, labels, scratch, loss, + XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, + logits, labels, scratch, loss, backprop); } }; diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 1f4e9753c3..b9d5104857 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1062,12 +1062,22 @@ REGISTER_OP("SoftmaxCrossEntropyWithLogits") .Attr("T: {half, bfloat16, float, double}") .SetShapeFn([](InferenceContext* c) { ShapeHandle input; - TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &input)); - TF_RETURN_IF_ERROR(c->Merge(input, c->input(1), &input)); + if (c->WithRank(c->input(0), 2, &input) == Status::OK() && + c->Merge(input, c->input(1), &input) == Status::OK()) { + DimensionHandle batch_size = c->Dim(input, 0); + c->set_output(0, c->Vector(batch_size)); + c->set_output(1, input); + return Status::OK(); + } + TF_RETURN_IF_ERROR(BroadcastBinaryOpOutputShapeFn(c, 1)); - DimensionHandle batch_size = c->Dim(input, 0); + if (!c->RankKnown(c->output(1)) || c->Rank(c->output(1)) != 2) { + return errors::InvalidArgument( + "Shape must be broadcasted with rank 2, but is rank ", + c->Rank(c->output(1))); + } + DimensionHandle batch_size = c->Dim(c->output(1), 0); c->set_output(0, c->Vector(batch_size)); - c->set_output(1, input); return Status::OK(); }); @@ -1155,9 +1165,9 @@ Status TopKShapeFn(InferenceContext* c) { DimensionHandle last_dim = c->Dim(input, -1); if (c->ValueKnown(last_dim) && c->ValueKnown(k_dim) && c->Value(last_dim) < c->Value(k_dim)) { - return errors::InvalidArgument( - "input must have last dimension >= k = ", c->Value(k_dim), " but is ", - c->Value(last_dim)); + return errors::InvalidArgument("input must have last dimension >= k = ", + c->Value(k_dim), " but is ", + c->Value(last_dim)); } // Replace last_dim with k_dim. @@ -1211,9 +1221,9 @@ REGISTER_OP("NthElement") DimensionHandle last_dim = c->Dim(input, -1); if (c->ValueKnown(last_dim) && c->ValueKnown(n_dim) && c->Value(last_dim) <= c->Value(n_dim)) { - return errors::InvalidArgument( - "Input must have last dimension > n = ", c->Value(n_dim), - " but is ", c->Value(last_dim)); + return errors::InvalidArgument("Input must have last dimension > n = ", + c->Value(n_dim), " but is ", + c->Value(last_dim)); } // Reduce last_dim for output tensor diff --git a/tensorflow/core/ops/nn_ops_test.cc b/tensorflow/core/ops/nn_ops_test.cc index 1b17a7cda6..289b953055 100644 --- a/tensorflow/core/ops/nn_ops_test.cc +++ b/tensorflow/core/ops/nn_ops_test.cc @@ -410,10 +410,18 @@ TEST(NNOpsTest, SoftmaxCrossEntropyWithLogits_ShapeFn) { INFER_OK(op, "[1,?];[?,2]", "[d0_0];[d0_0,d0_1|d1_1]"); INFER_OK(op, "[?,2];[1,2]", "[d1_0];in1"); - INFER_ERROR("Dimension 0 in both shapes must be equal, but are 1 and 2", op, - "[1,?];[2,?]"); - INFER_ERROR("Shape must be rank 2 but is rank 3", op, "[1,2,3];?"); - INFER_ERROR("Shapes must be equal rank, but are 2 and 3", op, "?;[1,2,3]"); + INFER_ERROR("Shape must be broadcasted with rank 2", op, "[1,2,3];?"); + INFER_ERROR("Shape must be broadcasted with rank 2", op, "?;[1,2,3]"); + + // Broadcast example + // [1,4] and [2,4] are broadcasted to [2,4] + INFER_OK(op, "[1,4];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); + // [2,4] and [2,1] are broadcasted to [2,4] + INFER_OK(op, "[2,4];[2,1]", "[d0_0];[d0_0|d1_0,d0_1]"); + // [1,?] and [2,4] are broadcasted to [2,4] + INFER_OK(op, "[1,?];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); + // [2,4] and [?,1] are broadcasted to [2,4] + INFER_OK(op, "[2,4];[?,1]", "[d0_0];[d0_0|d1_0,d0_1]"); } TEST(NNOpsTest, SparseSoftmaxCrossEntropyWithLogits_ShapeFn) { diff --git a/tensorflow/python/kernel_tests/xent_op_test.py b/tensorflow/python/kernel_tests/xent_op_test.py index e3e120a4eb..60c726d54c 100644 --- a/tensorflow/python/kernel_tests/xent_op_test.py +++ b/tensorflow/python/kernel_tests/xent_op_test.py @@ -18,10 +18,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import itertools +import sys + import numpy as np +from tensorflow.python.client import session from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl @@ -88,7 +94,7 @@ class XentTest(test.TestCase): 4.]]]).astype(dtype) np_labels = np.array([[[0., 0., 0., 1.]], [[0., .5, .5, 0.]]]).astype(dtype) - self.assertRaisesRegexp(ValueError, "must be rank 2", + self.assertRaisesRegexp(ValueError, "rank 2, but is rank 3", gen_nn_ops.softmax_cross_entropy_with_logits, np_features, np_labels) @@ -128,6 +134,24 @@ class XentTest(test.TestCase): self.assertAllClose( np.array([1.3862, 1.9401]), np_loss, rtol=1.e-3, atol=1.e-3) + def testShapeBroadcast(self): + np_f = np.array([[1., 2., 3., 4.], + [1., 2., 3., 4.]]).astype(np.float32) + np_l = np.array([[0., 0., 0., 1.], + [0., .5, .5, 0.]]).astype(np.float32) + np_loss, np_backprop = self._npXent(np_f, np_l) + tf_f = constant_op.constant( + np.array([[1., 2., 3., 4.]]).astype(np.float32)) + tf_l = constant_op.constant( + np.array([[0., 0., 0., 1.], [0., .5, .5, 0.]]).astype(np.float32)) + for use_gpu in [False, True]: + with self.test_session(use_gpu=use_gpu) as sess: + loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( + tf_f, tf_l) + tf_loss, tf_backprop = sess.run([loss, backprop]) + self.assertAllCloseAccordingToType(np_loss, tf_loss) + self.assertAllCloseAccordingToType(np_backprop, tf_backprop) + def testShapeMismatch(self): with self.test_session(): with self.assertRaises(ValueError): @@ -260,5 +284,60 @@ class XentTest(test.TestCase): self.assertAllEqual(np_loss, tf_loss) +class XentBenchmark(test.Benchmark): + + def benchmarkZeroDimension(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "zero_dimension_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + logits = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + def benchmarkSingleClass(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "single_class_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = constant_op.constant([[1.], [-1.], [0.]], + dtype=dtypes.float32) + logits = constant_op.constant([[-1.], [0.], [1.]], + dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + if __name__ == "__main__": test.main() -- GitLab From 31232f29daffe0e496bea22dffeda9e7945d344c Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Tue, 27 Mar 2018 12:55:56 -0700 Subject: [PATCH 283/906] [TF:XLA] Force DebugOptions to be specified when calling HloModule::CreateModuleConfigFromProto Otherwise it's easy to forget that you likely want the DebugOptions to be `legacy_flags::GetDebugOptionsFromFlags()`. PiperOrigin-RevId: 190659046 --- tensorflow/compiler/xla/client/xla_client/BUILD | 1 + .../compiler/xla/client/xla_client/xla_builder_test.cc | 4 +++- tensorflow/compiler/xla/service/hlo_module.cc | 3 ++- tensorflow/compiler/xla/service/hlo_module.h | 2 +- tensorflow/compiler/xla/service/hlo_runner.cc | 7 +++---- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tensorflow/compiler/xla/client/xla_client/BUILD b/tensorflow/compiler/xla/client/xla_client/BUILD index cc5f551c9c..60f13e04cb 100644 --- a/tensorflow/compiler/xla/client/xla_client/BUILD +++ b/tensorflow/compiler/xla/client/xla_client/BUILD @@ -70,6 +70,7 @@ tf_cc_test( "//tensorflow/compiler/xla:test", "//tensorflow/compiler/xla:test_helpers", "//tensorflow/compiler/xla:xla_data_proto", + "//tensorflow/compiler/xla/legacy_flags:debug_options_flags", "//tensorflow/compiler/xla/service:hlo", "//tensorflow/compiler/xla/service:hlo_matchers", "//tensorflow/core:test", diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc index 85d4227ba4..ce984564d0 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc @@ -17,6 +17,7 @@ limitations under the License. #include +#include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h" #include "tensorflow/compiler/xla/service/hlo_matchers.h" #include "tensorflow/compiler/xla/service/hlo_module.h" #include "tensorflow/compiler/xla/shape_util.h" @@ -39,7 +40,8 @@ class XlaBuilderTest : public ::testing::Test { TF_ASSIGN_OR_RETURN(XlaComputation computation, b->Build()); const HloModuleProto& proto = computation.proto(); TF_ASSIGN_OR_RETURN(const auto& config, - HloModule::CreateModuleConfigFromProto(proto)); + HloModule::CreateModuleConfigFromProto( + proto, legacy_flags::GetDebugOptionsFromFlags())); return HloModule::CreateFromProto(proto, config); } diff --git a/tensorflow/compiler/xla/service/hlo_module.cc b/tensorflow/compiler/xla/service/hlo_module.cc index 595c531ccf..08b9a29aed 100644 --- a/tensorflow/compiler/xla/service/hlo_module.cc +++ b/tensorflow/compiler/xla/service/hlo_module.cc @@ -295,12 +295,13 @@ StatusOr> HloModule::CreateFromProto( /* static */ StatusOr HloModule::CreateModuleConfigFromProto( - const HloModuleProto& module) { + const HloModuleProto& module, const DebugOptions& debug_options) { TF_RET_CHECK(module.has_program_shape()) << "No program shape found in the proto"; const auto& program_shape = module.program_shape(); HloModuleConfig module_config(program_shape); + module_config.set_debug_options(debug_options); // The module config is constructed with default layouts regardless of what is // passed in via the ProgramShape. Set the layouts to the appropriate values. diff --git a/tensorflow/compiler/xla/service/hlo_module.h b/tensorflow/compiler/xla/service/hlo_module.h index 755bbd359f..9f7f25202b 100644 --- a/tensorflow/compiler/xla/service/hlo_module.h +++ b/tensorflow/compiler/xla/service/hlo_module.h @@ -172,7 +172,7 @@ class HloModule { // Creates and returns an HloModuleConfig with an appropriate program shape // for the HLO module in the given proto. static StatusOr CreateModuleConfigFromProto( - const HloModuleProto& module); + const HloModuleProto& module, const DebugOptions& debug_options); // Outlines the given expression from the given computation. // instructions_to_outline contains the instructions that form the expression. diff --git a/tensorflow/compiler/xla/service/hlo_runner.cc b/tensorflow/compiler/xla/service/hlo_runner.cc index e5b1c2efa3..ec7d8210a7 100644 --- a/tensorflow/compiler/xla/service/hlo_runner.cc +++ b/tensorflow/compiler/xla/service/hlo_runner.cc @@ -52,10 +52,9 @@ namespace { // Creates an HloModule from the given proto. StatusOr> HloProtoToModule( const HloProto& proto, const DebugOptions& debug_options) { - TF_ASSIGN_OR_RETURN( - HloModuleConfig config, - HloModule::CreateModuleConfigFromProto(proto.hlo_module())); - config.set_debug_options(debug_options); + TF_ASSIGN_OR_RETURN(HloModuleConfig config, + HloModule::CreateModuleConfigFromProto(proto.hlo_module(), + debug_options)); TF_ASSIGN_OR_RETURN(auto module, HloModule::CreateFromProto(proto.hlo_module(), config)); return std::move(module); -- GitLab From 083cf6b91a380641933457a4301f9b1efa13af92 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 25 Oct 2017 17:03:15 +0000 Subject: [PATCH 284/906] Add customerized kernel implementation for clip_by_value This fix tries to address the issue raised in 7225 where `tf.clip_by_value` does not have a custom kernel and reused `tf.maximum` and `tf.mimimum`. In case scalar values are passed to `tf.clip_by_value`, unnecessary memory might incur. This fix adds the customerized kernel implementation for `tf.clip_by_value`. This fix fixes 7225. Signed-off-by: Yong Tang --- tensorflow/core/kernels/cwise_op_clip.cc | 150 +++++++++++++++++++++++ tensorflow/core/ops/math_ops.cc | 23 ++++ 2 files changed, 173 insertions(+) create mode 100644 tensorflow/core/kernels/cwise_op_clip.cc diff --git a/tensorflow/core/kernels/cwise_op_clip.cc b/tensorflow/core/kernels/cwise_op_clip.cc new file mode 100644 index 0000000000..6ce062b08f --- /dev/null +++ b/tensorflow/core/kernels/cwise_op_clip.cc @@ -0,0 +1,150 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/kernels/cwise_ops_common.h" + +//#include "third_party/eigen3/Eigen/Core/CwiseTernaryOp.h" + +namespace tensorflow { + +// Unary functor for clip +template +struct UnaryClipOp { + UnaryClipOp(const T& value_min, const T& value_max) + : value_min_(value_min), value_max_(value_max) {} + const T operator()(const T& value) const { + return std::max(std::min(value, value_max_), value_min_); + } + T value_min_; + T value_max_; +}; + +// Binary functor for clip +template +struct BinaryClipMinOp { + BinaryClipMinOp(const T& value_min) : value_min_(value_min) {} + const T operator()(const T& value, const T& value_max) const { + return std::max(std::min(value, value_max), value_min_); + } + T value_min_; +}; + +// Binary functor for clip +template +struct BinaryClipMaxOp { + BinaryClipMaxOp(const T& value_max) : value_max_(value_max) {} + const T operator()(const T& value, const T& value_min) const { + return std::max(std::min(value, value_max_), value_min); + } + T value_max_; +}; + +// Basic coefficient-wise tenary operations. +// This is the case for example of the clip_by_value. +// Device: E.g., CPUDevice, GPUDevice. +// Functor: defined above. E.g., functor::clip. +template +class TenaryOp : public OpKernel { + public: + explicit TenaryOp(OpKernelConstruction* ctx) : OpKernel(ctx) {} + + void Compute(OpKernelContext* ctx) override { + const Tensor& in0 = ctx->input(0); + const Tensor& in1 = ctx->input(1); + const Tensor& in2 = ctx->input(2); + + auto in0_flat = in0.flat(); + auto in1_flat = in1.flat(); + auto in2_flat = in2.flat(); + const Device& d = ctx->eigen_device(); + + Tensor* out = nullptr; + OP_REQUIRES_OK( + ctx, ctx->forward_input_or_allocate_output({0}, 0, in0.shape(), &out)); + auto out_flat = out->flat(); + if (in1.shape() == in2.shape()) { + if (in0.shape() == in1.shape()) { + out_flat = in0_flat.cwiseMin(in2_flat).cwiseMax(in1_flat); + } else { + OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(in1.shape()), + errors::InvalidArgument( + "clip_value_min and clip_value_max must be either of " + "the same shape as input, or a scalar. ", + "input shape: ", in0.shape().DebugString(), + "clip_value_min shape: ", in1.shape().DebugString(), + "clip_value_max shape: ", in2.shape().DebugString())); + out_flat = in0_flat.unaryExpr(UnaryClipOp(in1_flat(0), in2_flat(0))); + } + } else { + if (in0.shape() == in1.shape()) { + OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(in2.shape()), + errors::InvalidArgument( + "clip_value_min and clip_value_max must be either of " + "the same shape as input, or a scalar. ", + "input shape: ", in0.shape().DebugString(), + "clip_value_min shape: ", in1.shape().DebugString(), + "clip_value_max shape: ", in2.shape().DebugString())); + out_flat = + in0_flat.binaryExpr(in1_flat, BinaryClipMaxOp(in2_flat(0))); + + } else { + OP_REQUIRES(ctx, (in0.shape() == in2.shape() && + TensorShapeUtils::IsScalar(in1.shape())), + errors::InvalidArgument( + "clip_value_min and clip_value_max must be either of " + "the same shape as input, or a scalar. ", + "input shape: ", in0.shape().DebugString(), + "clip_value_min shape: ", in1.shape().DebugString(), + "clip_value_max shape: ", in2.shape().DebugString())); + out_flat = + in0_flat.binaryExpr(in2_flat, BinaryClipMinOp(in1_flat(0))); + } + } + } +}; + +#define REGISTER_CPU_KERNEL(type) \ + REGISTER_KERNEL_BUILDER( \ + Name("ClipByValue").Device(DEVICE_CPU).TypeConstraint("T"), \ + TenaryOp); + +REGISTER_CPU_KERNEL(Eigen::half); +REGISTER_CPU_KERNEL(float); +REGISTER_CPU_KERNEL(double); +REGISTER_CPU_KERNEL(int8); +REGISTER_CPU_KERNEL(int16); +REGISTER_CPU_KERNEL(int32); +REGISTER_CPU_KERNEL(int64); +REGISTER_CPU_KERNEL(uint8); +REGISTER_CPU_KERNEL(uint16); + +#undef REGISTER_CPU_KERNEL + +#if GOOGLE_CUDA +// REGISTER3(BinaryOp, GPU, "Add", functor::add, float, Eigen::half, double); + +// A special GPU kernel for int32. +// TODO(b/25387198): Also enable int32 in device memory. This kernel +// registration requires all int32 inputs and outputs to be in host memory. +REGISTER_KERNEL_BUILDER(Name("ClipByValue") + .Device(DEVICE_GPU) + .HostMemory("t") + .HostMemory("clip_value_min") + .HostMemory("clip_value_min") + .TypeConstraint("T"), + TenaryOp); +#endif + +} // namespace tensorflow diff --git a/tensorflow/core/ops/math_ops.cc b/tensorflow/core/ops/math_ops.cc index 8f33d51d5a..602a6ec115 100644 --- a/tensorflow/core/ops/math_ops.cc +++ b/tensorflow/core/ops/math_ops.cc @@ -1558,6 +1558,29 @@ REGISTER_OP("Bucketize") .Attr("boundaries: list(float)") .SetShapeFn(shape_inference::UnchangedShape); +REGISTER_OP("ClipByValue") + .Input("t: T") + .Input("clip_value_min: T") + .Input("clip_value_max: T") + .Output("output: T") + .Attr("T: numbertype") + .SetShapeFn(shape_inference::UnchangedShape) + .Doc(R"doc( +Clips tensor values to a specified min and max. + +Given a tensor `t`, this operation returns a tensor of the same type and +shape as `t` with its values clipped to `clip_value_min` and `clip_value_max`. +Any values less than `clip_value_min` are set to `clip_value_min`. Any values +greater than `clip_value_max` are set to `clip_value_max`. + +t: A `Tensor`. +clip_value_min: A 0-D (scalar) `Tensor`, or a `Tensor` with the same shape + as `t`. The minimum value to clip by. +clip_value_max: A 0-D (scalar) `Tensor`, or a `Tensor` with the same shape + as `t`. The maximum value to clip by. +output: A clipped `Tensor` with the same shape as input 't'. +)doc"); + #ifdef INTEL_MKL REGISTER_OP("_MklAddN") .Input("inputs: N * T") -- GitLab From daf0b206b5afde875a19270136ad22d9d2bb138c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 25 Oct 2017 17:08:32 +0000 Subject: [PATCH 285/906] Add python wrapper for tf.clip_by_value Signed-off-by: Yong Tang --- tensorflow/python/ops/clip_ops.py | 17 +- tensorflow/python/ops/hidden_ops.txt | 395 +++++++++++++++++++++++++++ 2 files changed, 400 insertions(+), 12 deletions(-) create mode 100644 tensorflow/python/ops/hidden_ops.txt diff --git a/tensorflow/python/ops/clip_ops.py b/tensorflow/python/ops/clip_ops.py index 49f8c66531..a5baebb3f6 100644 --- a/tensorflow/python/ops/clip_ops.py +++ b/tensorflow/python/ops/clip_ops.py @@ -26,6 +26,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_math_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import math_ops from tensorflow.python.util.tf_export import tf_export @@ -58,18 +59,10 @@ def clip_by_value(t, clip_value_min, clip_value_max, """ with ops.name_scope(name, "clip_by_value", [t, clip_value_min, clip_value_max]) as name: - t = ops.convert_to_tensor(t, name="t") - - # Go through list of tensors, for each value in each tensor clip - t_min = math_ops.minimum(t, clip_value_max) - # Assert that the shape is compatible with the initial shape, - # to prevent unintentional broadcasting. - _ = t.shape.merge_with(t_min.shape) - - t_max = math_ops.maximum(t_min, clip_value_min, name=name) - _ = t.shape.merge_with(t_max.shape) - - return t_max + return gen_math_ops._clip_by_value(t, + clip_value_min, + clip_value_max, + name=name) @tf_export("clip_by_norm") diff --git a/tensorflow/python/ops/hidden_ops.txt b/tensorflow/python/ops/hidden_ops.txt new file mode 100644 index 0000000000..e1217e984c --- /dev/null +++ b/tensorflow/python/ops/hidden_ops.txt @@ -0,0 +1,395 @@ +# array_ops +BatchToSpace +BroadcastArgs +BroadcastGradientArgs +ConcatOffset +Concat +ConcatV2 +ConjugateTranspose +Const +DebugGradientIdentity +DebugGradientRefIdentity +EditDistance +ExpandDims +ListDiff +MirrorPad +MirrorPadGrad +OneHot +Pack +Pad +PadV2 +ParallelConcat +Placeholder +RefIdentity +Reverse +Snapshot +SpaceToBatch +Split +SplitV +Squeeze +Slice +TileGrad # Exported through array_grad instead of array_ops. +ZerosLike # TODO(josh11b): Use this instead of the Python version. +Unique +UniqueV2 +UniqueWithCounts +UniqueWithCountsV2 +Unpack + +# candidate_sampling_ops +AllCandidateSampler +ComputeAccidentalHits +FixedUnigramCandidateSampler +LearnedUnigramCandidateSampler +LogUniformCandidateSampler +ThreadUnsafeUnigramCandidateSampler +UniformCandidateSampler + +# checkpoint_ops +GenerateVocabRemapping +LoadAndRemapMatrix + + +# control_flow_ops +Switch +Merge +RefMerge +Exit +RefExit + +# ctc_ops +CTCLoss +CTCGreedyDecoder +CTCBeamSearchDecoder + +# data_flow_ops +Barrier +BarrierClose +BarrierIncompleteSize +BarrierInsertMany +BarrierReadySize +BarrierTakeMany +DeleteSessionTensor +FakeQueue +FIFOQueue +FIFOQueueV2 +GetSessionHandle +GetSessionHandleV2 +GetSessionTensor +HashTable +HashTableV2 +InitializeTable +InitializeTableV2 +InitializeTableFromTextFile +InitializeTableFromTextFileV2 +LookupTableExport +LookupTableExportV2 +LookupTableFind +LookupTableFindV2 +LookupTableImport +LookupTableImportV2 +LookupTableInsert +LookupTableInsertV2 +LookupTableSize +LookupTableSizeV2 +MutableDenseHashTable +MutableDenseHashTableV2 +MutableHashTable +MutableHashTableV2 +MutableHashTableOfTensors +MutableHashTableOfTensorsV2 +Mutex +MutexAcquire +MutexRelease +PaddingFIFOQueue +PaddingFIFOQueueV2 +PriorityQueue +PriorityQueueV2 +QueueClose +QueueCloseV2 +QueueDequeue +QueueDequeueV2 +QueueDequeueMany +QueueDequeueManyV2 +QueueDequeueUpTo +QueueDequeueUpToV2 +QueueEnqueue +QueueEnqueueV2 +QueueEnqueueMany +QueueEnqueueManyV2 +QueueSize +QueueSizeV2 +RandomShuffleQueue +RandomShuffleQueueV2 +Stack +StackClose +StackPop +StackPush +StackV2 +StackCloseV2 +StackPopV2 +StackPushV2 +TensorArray +TensorArrayClose +TensorArrayCloseV2 +TensorArrayConcat +TensorArrayConcatV2 +TensorArrayGather +TensorArrayGatherV2 +TensorArrayGrad +TensorArrayGradV2 +TensorArrayPack +TensorArrayPackV2 +TensorArrayRead +TensorArrayReadV2 +TensorArrayScatter +TensorArrayScatterV2 +TensorArraySize +TensorArraySizeV2 +TensorArraySplit +TensorArraySplitV2 +TensorArrayUnpack +TensorArrayUnpackV2 +TensorArrayV2 +TensorArrayWrite +TensorArrayWriteV2 +TensorArrayV3 +TensorArrayCloseV3 +TensorArrayConcatV3 +TensorArrayGatherV3 +TensorArrayGradV3 +TensorArrayReadV3 +TensorArrayPackV3 +TensorArrayScatterV3 +TensorArraySizeV3 +TensorArraySplitV3 +TensorArrayUnpackV3 +TensorArrayWriteV3 + +# functional_ops +SymbolicGradient + +# image_ops +AdjustContrastv2 +NonMaxSuppression +NonMaxSuppressionV2 +RandomCrop +ResizeBilinearGrad +ResizeBicubicGrad +ResizeNearestNeighborGrad +SampleDistortedBoundingBox +SampleDistortedBoundingBoxV2 +ScaleImageGrad + +# io_ops +FixedLengthRecordReader +IdentityReader +ReaderNumRecordsProduced +ReaderNumWorkUnitsCompleted +ReaderRead +ReaderReadUpTo +ReaderReset +ReaderRestoreState +ReaderSerializeState +ReaderWorkQueueLength +FixedLengthRecordReaderV2 +IdentityReaderV2 +ReaderNumRecordsProducedV2 +ReaderNumWorkUnitsCompletedV2 +ReaderReadV2 +ReaderReadUpToV2 +ReaderResetV2 +ReaderRestoreStateV2 +ReaderSerializeStateV2 +ReaderWorkQueueLengthV2 +Restore +RestoreSlice +Save +SaveSlices +ShardedFilename +ShardedFilespec +TextLineReader +TFRecordReader +WholeFileReader +TextLineReaderV2 +TFRecordReaderV2 +WholeFileReaderV2 +LMDBReader +DecodeCSV + +# linalg_ops +BatchCholesky +BatchCholeskyGrad +BatchMatrixDeterminant +BatchMatrixInverse +BatchMatrixSolve +BatchMatrixSolveLs +BatchMatrixTriangularSolve +BatchSelfAdjointEig +BatchSelfAdjointEigV2 +BatchSvd +LogMatrixDeterminant +MatrixExponential +MatrixLogarithm +MatrixSolveLs +SelfAdjointEig +SelfAdjointEigV2 +Svd + +# logging_ops +Assert +AudioSummary +AudioSummaryV2 +HistogramSummary +ImageSummary +MergeSummary +Print +ScalarSummary +TensorSummary +TensorSummaryV2 + +# math_ops +Abs +AccumulateNV2 +AddN +AddV2 +All +Any +BatchMatMul +BatchFFT +BatchFFT2D +BatchFFT3D +BatchIFFT +BatchIFFT2D +BatchIFFT3D +Bucketize +ClipByValue +Complex +ComplexAbs +Conj +FloorDiv +FloorMod +HistogramFixedWidth +Max +Mean +Min +Mul +Neg +Pow +Prod +Range +RealDiv +Select +SparseMatMul +Sub +Sum +MatMul +Sigmoid +Tanh +SigmoidGrad +TanhGrad +InvGrad +ReciprocalGrad +SqrtGrad +RsqrtGrad +TruncateDiv +TruncateMod + +# nn_ops +AvgPoolGrad # "*Grad" accessible through nn_grad instead of nn_ops. +AvgPool3DGrad +BatchNormWithGlobalNormalization +BatchNormWithGlobalNormalizationGrad +FusedBatchNorm +FusedBatchNormV2 +SoftmaxCrossEntropyWithLogits +SparseSoftmaxCrossEntropyWithLogits +LRNGrad +MaxPoolGrad +MaxPoolGradWithArgmax +MaxPoolGradGrad +MaxPoolGradGradWithArgmax +MaxPool3DGrad +MaxPool3DGradGrad +ReluGrad +Relu6Grad +EluGrad +SeluGrad +SoftplusGrad +SoftsignGrad +TopK +TopKV2 +BiasAdd +BiasAddV1 +Relu6 +AvgPool +MaxPool +MaxPoolV2 +Softmax +LogSoftmax +FractionalAvgPoolGrad +FractionalMaxPoolGrad +InTopK +InTopKV2 + +# parsing_ops +ParseExample +ParseSingleSequenceExample + +# random_ops +RandomGamma +RandomPoisson +RandomUniform +RandomUniformInt +RandomShuffle +RandomStandardNormal +ParameterizedTruncatedNormal +TruncatedNormal + +# script_ops +PyFunc +PyFuncStateless +EagerPyFunc + +# sdca_ops + +# state_ops +Variable +VariableV2 +TemporaryVariable +DestroyTemporaryVariable + +# sparse_ops +AddSparseToTensorsMap +AddManySparseToTensorsMap +TakeManySparseFromTensorsMap +DeserializeManySparse +DeserializeSparse +SerializeManySparse +SerializeSparse +SparseAdd +SparseAddGrad +SparseConcat +SparseCross +SparseFillEmptyRows +SparseFillEmptyRowsGrad +SparseSplit +SparseSelectLastK +SparseReorder +SparseReshape +SparseToDense +SparseTensorDenseAdd +SparseTensorDenseMatMul + +# string_ops +StringSplit + +# user_ops +Fact + +# training_ops +# (None) + +# word2vec deprecated ops +NegTrain +Skipgram -- GitLab From 90a271e7a37574fc1c90fd6042c3b3972645d114 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 25 Oct 2017 17:09:05 +0000 Subject: [PATCH 286/906] Update tests for `tf.clip_by_value` Signed-off-by: Yong Tang --- tensorflow/python/kernel_tests/clip_ops_test.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/kernel_tests/clip_ops_test.py b/tensorflow/python/kernel_tests/clip_ops_test.py index 5c8b71da17..d47930350e 100644 --- a/tensorflow/python/kernel_tests/clip_ops_test.py +++ b/tensorflow/python/kernel_tests/clip_ops_test.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import constant_op +from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.ops import clip_ops from tensorflow.python.platform import test @@ -42,10 +43,12 @@ class ClipTest(test.TestCase): x = constant_op.constant([-5.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3, 1]) # Use a nonsensical shape. clip = constant_op.constant([1.0, 2.0]) - with self.assertRaises(ValueError): - _ = clip_ops.clip_by_value(x, -clip, clip) - with self.assertRaises(ValueError): - _ = clip_ops.clip_by_value(x, 1.0, clip) + with self.assertRaises(errors_impl.InvalidArgumentError): + ans = clip_ops.clip_by_value(x, -clip, clip) + tf_ans = ans.eval() + with self.assertRaises(errors_impl.InvalidArgumentError): + ans = clip_ops.clip_by_value(x, 1.0, clip) + tf_ans = ans.eval() def testClipByValueNonFinite(self): with self.test_session(): -- GitLab From cff8abcb1a9305491637dc44559316aa1d8184e6 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 26 Oct 2017 04:37:55 +0000 Subject: [PATCH 287/906] Add GPU kernel for tf.clip_by_value Signed-off-by: Yong Tang --- tensorflow/core/kernels/cwise_op_clip.cc | 162 +++++++++++++----- tensorflow/core/kernels/cwise_op_clip.h | 61 +++++++ .../core/kernels/cwise_op_clip_gpu.cu.cc | 134 +++++++++++++++ 3 files changed, 313 insertions(+), 44 deletions(-) create mode 100644 tensorflow/core/kernels/cwise_op_clip.h create mode 100644 tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc diff --git a/tensorflow/core/kernels/cwise_op_clip.cc b/tensorflow/core/kernels/cwise_op_clip.cc index 6ce062b08f..c2980acdd8 100644 --- a/tensorflow/core/kernels/cwise_op_clip.cc +++ b/tensorflow/core/kernels/cwise_op_clip.cc @@ -13,43 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/core/kernels/cwise_ops_common.h" - -//#include "third_party/eigen3/Eigen/Core/CwiseTernaryOp.h" +#include "tensorflow/core/kernels/cwise_op_clip.h" namespace tensorflow { -// Unary functor for clip -template -struct UnaryClipOp { - UnaryClipOp(const T& value_min, const T& value_max) - : value_min_(value_min), value_max_(value_max) {} - const T operator()(const T& value) const { - return std::max(std::min(value, value_max_), value_min_); - } - T value_min_; - T value_max_; -}; - -// Binary functor for clip -template -struct BinaryClipMinOp { - BinaryClipMinOp(const T& value_min) : value_min_(value_min) {} - const T operator()(const T& value, const T& value_max) const { - return std::max(std::min(value, value_max), value_min_); - } - T value_min_; -}; - -// Binary functor for clip -template -struct BinaryClipMaxOp { - BinaryClipMaxOp(const T& value_max) : value_max_(value_max) {} - const T operator()(const T& value, const T& value_min) const { - return std::max(std::min(value, value_max_), value_min); - } - T value_max_; -}; +typedef Eigen::ThreadPoolDevice CPUDevice; +typedef Eigen::GpuDevice GPUDevice; // Basic coefficient-wise tenary operations. // This is the case for example of the clip_by_value. @@ -76,7 +45,8 @@ class TenaryOp : public OpKernel { auto out_flat = out->flat(); if (in1.shape() == in2.shape()) { if (in0.shape() == in1.shape()) { - out_flat = in0_flat.cwiseMin(in2_flat).cwiseMax(in1_flat); + functor::TernaryClipOp()(d, in0_flat, in1_flat, in2_flat, + out_flat); } else { OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(in1.shape()), errors::InvalidArgument( @@ -85,7 +55,8 @@ class TenaryOp : public OpKernel { "input shape: ", in0.shape().DebugString(), "clip_value_min shape: ", in1.shape().DebugString(), "clip_value_max shape: ", in2.shape().DebugString())); - out_flat = in0_flat.unaryExpr(UnaryClipOp(in1_flat(0), in2_flat(0))); + functor::UnaryClipOp()(d, in0_flat, in1_flat, in2_flat, + out_flat); } } else { if (in0.shape() == in1.shape()) { @@ -96,9 +67,8 @@ class TenaryOp : public OpKernel { "input shape: ", in0.shape().DebugString(), "clip_value_min shape: ", in1.shape().DebugString(), "clip_value_max shape: ", in2.shape().DebugString())); - out_flat = - in0_flat.binaryExpr(in1_flat, BinaryClipMaxOp(in2_flat(0))); - + functor::BinaryLeftClipOp()(d, in0_flat, in1_flat, in2_flat, + out_flat); } else { OP_REQUIRES(ctx, (in0.shape() == in2.shape() && TensorShapeUtils::IsScalar(in1.shape())), @@ -108,13 +78,103 @@ class TenaryOp : public OpKernel { "input shape: ", in0.shape().DebugString(), "clip_value_min shape: ", in1.shape().DebugString(), "clip_value_max shape: ", in2.shape().DebugString())); - out_flat = - in0_flat.binaryExpr(in2_flat, BinaryClipMinOp(in1_flat(0))); + functor::BinaryRightClipOp()(d, in0_flat, in1_flat, in2_flat, + out_flat); } } } }; +namespace functor { +// Unary functor for clip [Tensor, Scalar, Scalar] +template +struct UnaryClipFunc { + UnaryClipFunc(const T& value_min, const T& value_max) + : value_min_(value_min), value_max_(value_max) {} + const T operator()(const T& value) const { + return std::max(std::min(value, value_max_), value_min_); + } + T value_min_; + T value_max_; +}; +template +struct UnaryClipOp { + void operator()(const CPUDevice& d, typename TTypes::ConstFlat& in0_flat, + typename TTypes::ConstFlat& in1_flat, + typename TTypes::ConstFlat& in2_flat, + typename TTypes::Flat& out_flat) const { + out_flat = in0_flat.unaryExpr(UnaryClipFunc(in1_flat(0), in2_flat(0))); + } +}; + +// Binary functor for clip [Tensor, Scalar, Tensor] +template +struct BinaryRightClipFunc { + BinaryRightClipFunc(const T& value_min) : value_min_(value_min) {} + const T operator()(const T& value, const T& value_max) const { + return std::max(std::min(value, value_max), value_min_); + } + T value_min_; +}; +template +struct BinaryRightClipOp { + void operator()(const CPUDevice& d, typename TTypes::ConstFlat& in0_flat, + typename TTypes::ConstFlat& in1_flat, + typename TTypes::ConstFlat& in2_flat, + typename TTypes::Flat& out_flat) const { + out_flat = + in0_flat.binaryExpr(in2_flat, BinaryRightClipFunc(in1_flat(0))); + } +}; + +// Binary functor for clip [Tensor, Tensor, Scalar] +template +struct BinaryLeftClipFunc { + BinaryLeftClipFunc(const T& value_max) : value_max_(value_max) {} + const T operator()(const T& value, const T& value_min) const { + return std::max(std::min(value, value_max_), value_min); + } + T value_max_; +}; +template +struct BinaryLeftClipOp { + void operator()(const CPUDevice& d, typename TTypes::ConstFlat& in0_flat, + typename TTypes::ConstFlat& in1_flat, + typename TTypes::ConstFlat& in2_flat, + typename TTypes::Flat& out_flat) const { + out_flat = + in0_flat.binaryExpr(in1_flat, BinaryLeftClipFunc(in2_flat(0))); + } +}; + +// Ternary functor for clip [Tensor, Tensor, Tensor] +template +struct TernaryClipOp { + void operator()(const CPUDevice& d, typename TTypes::ConstFlat& in0_flat, + typename TTypes::ConstFlat& in1_flat, + typename TTypes::ConstFlat& in2_flat, + typename TTypes::Flat& out_flat) const { + out_flat.device(d) = in0_flat.cwiseMin(in2_flat).cwiseMax(in1_flat); + } +}; + +#define INSTANTIATE_CPU(T) \ + template struct UnaryClipOp; \ + template struct BinaryRightClipOp; \ + template struct BinaryLeftClipOp; \ + template struct TernaryClipOp; +INSTANTIATE_CPU(Eigen::half); +INSTANTIATE_CPU(float); +INSTANTIATE_CPU(double); +INSTANTIATE_CPU(int8); +INSTANTIATE_CPU(int16); +INSTANTIATE_CPU(int32); +INSTANTIATE_CPU(int64); +INSTANTIATE_CPU(uint8); +INSTANTIATE_CPU(uint16); +#undef INSTANTIATE_CPU +} // namespace functor + #define REGISTER_CPU_KERNEL(type) \ REGISTER_KERNEL_BUILDER( \ Name("ClipByValue").Device(DEVICE_CPU).TypeConstraint("T"), \ @@ -129,11 +189,22 @@ REGISTER_CPU_KERNEL(int32); REGISTER_CPU_KERNEL(int64); REGISTER_CPU_KERNEL(uint8); REGISTER_CPU_KERNEL(uint16); - #undef REGISTER_CPU_KERNEL #if GOOGLE_CUDA -// REGISTER3(BinaryOp, GPU, "Add", functor::add, float, Eigen::half, double); + +#define REGISTER_GPU_KERNEL(type) \ + REGISTER_KERNEL_BUILDER( \ + Name("ClipByValue").Device(DEVICE_GPU).TypeConstraint("T"), \ + TenaryOp); +REGISTER_GPU_KERNEL(Eigen::half); +REGISTER_GPU_KERNEL(float); +REGISTER_GPU_KERNEL(double); +REGISTER_GPU_KERNEL(int8); +REGISTER_GPU_KERNEL(int16); +REGISTER_GPU_KERNEL(int64); +REGISTER_GPU_KERNEL(uint8); +REGISTER_GPU_KERNEL(uint16); // A special GPU kernel for int32. // TODO(b/25387198): Also enable int32 in device memory. This kernel @@ -142,9 +213,12 @@ REGISTER_KERNEL_BUILDER(Name("ClipByValue") .Device(DEVICE_GPU) .HostMemory("t") .HostMemory("clip_value_min") - .HostMemory("clip_value_min") + .HostMemory("clip_value_max") + .HostMemory("output") .TypeConstraint("T"), TenaryOp); + +#undef REGISTER_GPU_KERNEL #endif } // namespace tensorflow diff --git a/tensorflow/core/kernels/cwise_op_clip.h b/tensorflow/core/kernels/cwise_op_clip.h new file mode 100644 index 0000000000..1a4bf8cf1d --- /dev/null +++ b/tensorflow/core/kernels/cwise_op_clip.h @@ -0,0 +1,61 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_KERNELS_CWISE_OP_CLIP_H_ +#define TENSORFLOW_KERNELS_CWISE_OP_CLIP_H_ + +#include "tensorflow/core/kernels/cwise_ops_common.h" + +namespace tensorflow { +namespace functor { +// Unary functor for clip [Tensor, Scalar, Scalar] +template +struct UnaryClipOp { + void operator()(const Device &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const; +}; + +// Binary functor for clip [Tensor, Scalar, Tensor] +template +struct BinaryRightClipOp { + void operator()(const Device &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const; +}; + +// Binary functor for clip [Tensor, Tensor, Scalar] +template +struct BinaryLeftClipOp { + void operator()(const Device &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const; +}; + +// Ternary functor for clip [Tensor, Tensor, Tensor] +template +struct TernaryClipOp { + void operator()(const Device &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const; +}; +} +} // namespace tensorflow + +#endif // TENSORFLOW_KERNELS_CWISE_OP_CLIP_H_ diff --git a/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc new file mode 100644 index 0000000000..5c07847548 --- /dev/null +++ b/tensorflow/core/kernels/cwise_op_clip_gpu.cu.cc @@ -0,0 +1,134 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#if GOOGLE_CUDA + +#define EIGEN_USE_GPU + +#include "tensorflow/core/kernels/cwise_op_clip.h" +#include "tensorflow/core/kernels/cwise_ops_gpu_common.cu.h" +#include "tensorflow/core/util/cuda_kernel_helper.h" + +namespace tensorflow { + +template +__global__ void UnaryClipCustomKernel(const int32 size_in, const T *in0, + const T *in1, const T *in2, T *out) { + CUDA_1D_KERNEL_LOOP(i, size_in) { + T value = in2[0] < in0[i] ? in2[0] : in0[i]; + out[i] = value < in1[0] ? in1[0] : value; + } +} + +template +__global__ void BinaryRightClipCustomKernel(const int32 size_in, const T *in0, + const T *in1, const T *in2, + T *out) { + CUDA_1D_KERNEL_LOOP(i, size_in) { + T value = in2[i] < in0[i] ? in2[i] : in0[i]; + out[i] = value < in1[0] ? in1[0] : value; + } +} + +template +__global__ void BinaryLeftClipCustomKernel(const int32 size_in, const T *in0, + const T *in1, const T *in2, T *out) { + CUDA_1D_KERNEL_LOOP(i, size_in) { + T value = in2[0] < in0[i] ? in2[0] : in0[i]; + out[i] = value < in1[i] ? in1[i] : value; + } +} + +namespace functor { + +// Unary functor for clip [Tensor, Scalar, Scalar] +template +struct UnaryClipOp { + void operator()(const GPUDevice &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const { + CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); + + UnaryClipCustomKernel< + T><<>>( + in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); + } +}; + +// Binary functor for clip [Tensor, Scalar, Tensor] +template +struct BinaryRightClipOp { + void operator()(const GPUDevice &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const { + CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); + + BinaryRightClipCustomKernel< + T><<>>( + in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); + } +}; + +// Binary functor for clip [Tensor, Tensor, Scalar] +template +struct BinaryLeftClipOp { + void operator()(const GPUDevice &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const { + CudaLaunchConfig config = GetCudaLaunchConfig(in0_flat.size(), d); + + BinaryLeftClipCustomKernel< + T><<>>( + in0_flat.size(), in0_flat.data(), in1_flat.data(), in2_flat.data(), + out_flat.data()); + } +}; + +// Ternary functor for clip [Tensor, Tensor, Tensor] +template +struct TernaryClipOp { + void operator()(const GPUDevice &d, typename TTypes::ConstFlat &in0_flat, + typename TTypes::ConstFlat &in1_flat, + typename TTypes::ConstFlat &in2_flat, + typename TTypes::Flat &out_flat) const { + out_flat.device(d) = in0_flat.cwiseMin(in2_flat).cwiseMax(in1_flat); + } +}; + +#define INSTANTIATE_GPU(T) \ + template struct UnaryClipOp; \ + template struct BinaryRightClipOp; \ + template struct BinaryLeftClipOp; \ + template struct TernaryClipOp; +INSTANTIATE_GPU(Eigen::half); +INSTANTIATE_GPU(float); +INSTANTIATE_GPU(double); +INSTANTIATE_GPU(int8); +INSTANTIATE_GPU(int16); +INSTANTIATE_GPU(int32); +INSTANTIATE_GPU(int64); +INSTANTIATE_GPU(uint8); +INSTANTIATE_GPU(uint16); +#undef INSTANTIATE_GPU + +} // namespace functor +} // namespace tensorflow + +#endif // GOOGLE_CUDA -- GitLab From a3553d45b63fba1cd4eb8d1d5b6dd0d565c94879 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 26 Oct 2017 04:38:38 +0000 Subject: [PATCH 288/906] Update test cases for tf.clip_by_value Signed-off-by: Yong Tang --- .../python/kernel_tests/clip_ops_test.py | 105 ++++++++++++++---- 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/tensorflow/python/kernel_tests/clip_ops_test.py b/tensorflow/python/kernel_tests/clip_ops_test.py index d47930350e..2d03fb99e4 100644 --- a/tensorflow/python/kernel_tests/clip_ops_test.py +++ b/tensorflow/python/kernel_tests/clip_ops_test.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.ops import clip_ops @@ -29,7 +30,7 @@ class ClipTest(test.TestCase): # ClipByValue test def testClipByValue(self): - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-5.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3]) np_ans = [[-4.4, 2.0, 3.0], [4.0, 4.4, 4.4]] clip_value = 4.4 @@ -38,8 +39,72 @@ class ClipTest(test.TestCase): self.assertAllClose(np_ans, tf_ans) + # [Tensor, Scalar, Scalar] + def testClipByValue0Type(self): + for dtype in [dtypes.float16, dtypes.float32, dtypes.float64, + dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, + dtypes.uint8, dtypes.uint16]: + with self.test_session(use_gpu=True): + x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) + np_ans = [[2, 2, 3], [4, 4, 4]] + clip_value_min = 2 + clip_value_max = 4 + ans = clip_ops.clip_by_value(x, clip_value_min, clip_value_max) + tf_ans = ans.eval() + + self.assertAllClose(np_ans, tf_ans) + + # [Tensor, Tensor, Scalar] + def testClipByValue1Type(self): + for dtype in [dtypes.float16, dtypes.float32, dtypes.float64, + dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, + dtypes.uint8, dtypes.uint16]: + with self.test_session(use_gpu=True): + x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) + np_ans = [[2, 2, 3], [4, 4, 4]] + clip_value_min = constant_op.constant([2, 2, 2, 3, 3, 3], shape=[2, 3], + dtype=dtype) + clip_value_max = 4 + ans = clip_ops.clip_by_value(x, clip_value_min, clip_value_max) + tf_ans = ans.eval() + + self.assertAllClose(np_ans, tf_ans) + + # [Tensor, Scalar, Tensor] + def testClipByValue2Type(self): + for dtype in [dtypes.float16, dtypes.float32, dtypes.float64, + dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, + dtypes.uint8, dtypes.uint16]: + with self.test_session(use_gpu=True): + x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) + np_ans = [[4, 4, 4], [4, 5, 6]] + clip_value_min = 4 + clip_value_max = constant_op.constant([6, 6, 6, 6, 6, 6], shape=[2, 3], + dtype=dtype) + ans = clip_ops.clip_by_value(x, clip_value_min, clip_value_max) + tf_ans = ans.eval() + + self.assertAllClose(np_ans, tf_ans) + + # [Tensor, Tensor, Tensor] + def testClipByValue3Type(self): + for dtype in [dtypes.float16, dtypes.float32, dtypes.float64, + dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, + dtypes.uint8, dtypes.uint16]: + with self.test_session(use_gpu=True): + x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) + np_ans = [[2, 2, 3], [5, 5, 6]] + clip_value_min = constant_op.constant([2, 2, 2, 5, 5, 5], shape=[2, 3], + dtype=dtype) + clip_value_max = constant_op.constant([5, 5, 5, 7, 7, 7], shape=[2, 3], + dtype=dtype) + ans = clip_ops.clip_by_value(x, clip_value_min, clip_value_max) + tf_ans = ans.eval() + + self.assertAllClose(np_ans, tf_ans) + def testClipByValueBadShape(self): - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-5.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3, 1]) # Use a nonsensical shape. clip = constant_op.constant([1.0, 2.0]) @@ -51,7 +116,7 @@ class ClipTest(test.TestCase): tf_ans = ans.eval() def testClipByValueNonFinite(self): - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([float('NaN'), float('Inf'), -float('Inf')]) np_ans = [float('NaN'), 4.0, -4.0] clip_value = 4.0 @@ -63,7 +128,7 @@ class ClipTest(test.TestCase): # ClipByNorm tests def testClipByNormClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Norm of x = sqrt(3^2 + 4^2) = 5 np_ans = [[-2.4, 0.0, 0.0], [3.2, 0.0, 0.0]] @@ -79,7 +144,7 @@ class ClipTest(test.TestCase): self.assertAllClose(np_ans, tf_ans_tensor) def testClipByNormBadShape(self): - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3, 1]) # Use a nonsensical shape. clip = constant_op.constant([1.0, 2.0]) @@ -88,7 +153,7 @@ class ClipTest(test.TestCase): def testClipByNormNotClipped(self): # No norm clipping when clip_norm >= 5 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Norm of x = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 0.0]] @@ -100,7 +165,7 @@ class ClipTest(test.TestCase): def testClipByNormZero(self): # No norm clipping when norm = 0 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) # Norm = 0, no changes np_ans = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] @@ -112,7 +177,7 @@ class ClipTest(test.TestCase): def testClipByNormClippedWithDim0(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[:, 0] = sqrt(3^2 + 4^2) = 5, x[:, 2] = 3 np_ans = [[-2.4, 0.0, 0.0], [3.2, 0.0, 3.0]] @@ -124,7 +189,7 @@ class ClipTest(test.TestCase): def testClipByNormClippedWithDim1(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[0, :] = 3, x[1, :] = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [3.2, 0.0, 2.4]] @@ -136,7 +201,7 @@ class ClipTest(test.TestCase): def testClipByNormNotClippedWithAxes(self): # No norm clipping when clip_norm >= 5 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[0, :] = 3, x[1, :] = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 3.0]] @@ -149,7 +214,7 @@ class ClipTest(test.TestCase): # ClipByGlobalNorm tests def testClipByGlobalNormClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -170,7 +235,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormClippedTensor(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -191,7 +256,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormSupportsNone(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -214,7 +279,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormWithIndexedSlicesClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = ops.IndexedSlices( constant_op.constant([1.0, -2.0]), constant_op.constant([3, 4])) @@ -247,7 +312,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormNotClipped(self): # No norm clipping when clip_norm >= 5 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -266,7 +331,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormZero(self): # No norm clipping when norm = 0 - with self.test_session(): + with self.test_session(use_gpu=True): x0 = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([0.0, 0.0]) # Norm = 0, no changes @@ -285,7 +350,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormClipped(self): # Norm clipping when average clip_norm < 0.83333333 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-2.88, 0.0, 0.0], [3.84, 0.0, 0.0]] @@ -297,7 +362,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormClippedTensor(self): # Norm clipping when average clip_norm < 0.83333333 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-2.88, 0.0, 0.0], [3.84, 0.0, 0.0]] @@ -309,7 +374,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormNotClipped(self): # No norm clipping when average clip_norm >= 0.83333333 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 0.0]] @@ -321,7 +386,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormZero(self): # No norm clipping when average clip_norm = 0 - with self.test_session(): + with self.test_session(use_gpu=True): x = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) # Average norm = 0, no changes np_ans = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] -- GitLab From a5e9d9a387680b0b1d7d8ed08fc9c07477a7efe7 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 30 Oct 2017 23:42:08 +0000 Subject: [PATCH 289/906] Add grad registration for clip_by_value and address review feedbacks. Signed-off-by: Yong Tang --- tensorflow/core/kernels/cwise_op_clip.cc | 2 +- .../python/kernel_tests/clip_ops_test.py | 16 ++++++++++++ tensorflow/python/ops/clip_ops.py | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/cwise_op_clip.cc b/tensorflow/core/kernels/cwise_op_clip.cc index c2980acdd8..f30c49fdf8 100644 --- a/tensorflow/core/kernels/cwise_op_clip.cc +++ b/tensorflow/core/kernels/cwise_op_clip.cc @@ -1,4 +1,4 @@ -/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/tensorflow/python/kernel_tests/clip_ops_test.py b/tensorflow/python/kernel_tests/clip_ops_test.py index 2d03fb99e4..cb1359be15 100644 --- a/tensorflow/python/kernel_tests/clip_ops_test.py +++ b/tensorflow/python/kernel_tests/clip_ops_test.py @@ -23,11 +23,27 @@ from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.ops import clip_ops +from tensorflow.python.ops import gradient_checker from tensorflow.python.platform import test class ClipTest(test.TestCase): + def testClipByValueGradient(self): + inputs = constant_op.constant([1.0, 2.0, 3.0, 4.0], dtype=dtypes.float32) + outputs_1 = clip_ops.clip_by_value(inputs, 0.5, 3.5) + min_val = constant_op.constant([0.5, 0.5, 0.5, 0.5], dtype=dtypes.float32) + max_val = constant_op.constant([3.5, 3.5, 3.5, 3.5], dtype=dtypes.float32) + outputs_2 = clip_ops.clip_by_value(inputs, min_val, max_val) + with self.test_session(): + error_1 = gradient_checker.compute_gradient_error(inputs, [4], + outputs_1, [4]) + self.assertLess(error_1, 1e-4) + + error_2 = gradient_checker.compute_gradient_error(inputs, [4], + outputs_2, [4]) + self.assertLess(error_2, 1e-4) + # ClipByValue test def testClipByValue(self): with self.test_session(use_gpu=True): diff --git a/tensorflow/python/ops/clip_ops.py b/tensorflow/python/ops/clip_ops.py index a5baebb3f6..e84cfc6944 100644 --- a/tensorflow/python/ops/clip_ops.py +++ b/tensorflow/python/ops/clip_ops.py @@ -26,6 +26,7 @@ from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import gen_math_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import math_ops @@ -64,6 +65,30 @@ def clip_by_value(t, clip_value_min, clip_value_max, clip_value_max, name=name) +@ops.RegisterGradient("ClipByValue") +def _ClipByValueGrad(op, grad): + """Returns grad of clip_by_value.""" + x = op.inputs[0] + y = op.inputs[1] + z = op.inputs[2] + gdtype = grad.dtype + sx = array_ops.shape(x) + sy = array_ops.shape(y) + sz = array_ops.shape(z) + gradshape = array_ops.shape(grad) + zeros = array_ops.zeros(gradshape, gdtype) + xymask = math_ops.less(x, y) + xzmask = math_ops.greater(x, z) + rx, ry = gen_array_ops._broadcast_gradient_args(sx, sy) + rx, rz = gen_array_ops._broadcast_gradient_args(sx, sz) + xgrad = array_ops.where(math_ops.logical_or(xymask, xzmask), zeros, grad) + ygrad = array_ops.where(xymask, grad, zeros) + zgrad = array_ops.where(xzmask, grad, zeros) + gx = array_ops.reshape(math_ops.reduce_sum(xgrad, rx), sx) + gy = array_ops.reshape(math_ops.reduce_sum(ygrad, ry), sy) + gz = array_ops.reshape(math_ops.reduce_sum(zgrad, rz), sz) + return (gx, gy, gz) + @tf_export("clip_by_norm") def clip_by_norm(t, clip_norm, axes=None, name=None): -- GitLab From 71ddf90d3c8c49d4401c0d298bf63b92150dadaa Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 14 Dec 2017 04:06:58 +0000 Subject: [PATCH 290/906] Update with `TenaryOp` -> `ClipOp` Signed-off-by: Yong Tang --- tensorflow/core/kernels/cwise_op_clip.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/core/kernels/cwise_op_clip.cc b/tensorflow/core/kernels/cwise_op_clip.cc index f30c49fdf8..bd22f5777c 100644 --- a/tensorflow/core/kernels/cwise_op_clip.cc +++ b/tensorflow/core/kernels/cwise_op_clip.cc @@ -25,9 +25,9 @@ typedef Eigen::GpuDevice GPUDevice; // Device: E.g., CPUDevice, GPUDevice. // Functor: defined above. E.g., functor::clip. template -class TenaryOp : public OpKernel { +class ClipOp : public OpKernel { public: - explicit TenaryOp(OpKernelConstruction* ctx) : OpKernel(ctx) {} + explicit ClipOp(OpKernelConstruction* ctx) : OpKernel(ctx) {} void Compute(OpKernelContext* ctx) override { const Tensor& in0 = ctx->input(0); @@ -178,7 +178,7 @@ INSTANTIATE_CPU(uint16); #define REGISTER_CPU_KERNEL(type) \ REGISTER_KERNEL_BUILDER( \ Name("ClipByValue").Device(DEVICE_CPU).TypeConstraint("T"), \ - TenaryOp); + ClipOp); REGISTER_CPU_KERNEL(Eigen::half); REGISTER_CPU_KERNEL(float); @@ -196,7 +196,7 @@ REGISTER_CPU_KERNEL(uint16); #define REGISTER_GPU_KERNEL(type) \ REGISTER_KERNEL_BUILDER( \ Name("ClipByValue").Device(DEVICE_GPU).TypeConstraint("T"), \ - TenaryOp); + ClipOp); REGISTER_GPU_KERNEL(Eigen::half); REGISTER_GPU_KERNEL(float); REGISTER_GPU_KERNEL(double); @@ -216,7 +216,7 @@ REGISTER_KERNEL_BUILDER(Name("ClipByValue") .HostMemory("clip_value_max") .HostMemory("output") .TypeConstraint("T"), - TenaryOp); + ClipOp); #undef REGISTER_GPU_KERNEL #endif -- GitLab From d1078b562532e2de60bc16fc544a94823149ae77 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 18 Dec 2017 17:42:37 +0000 Subject: [PATCH 291/906] Fix failing test //tensorflow/python:function_test Signed-off-by: Yong Tang --- tensorflow/python/framework/function_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/framework/function_test.py b/tensorflow/python/framework/function_test.py index 65ca801cbe..24aaff3748 100644 --- a/tensorflow/python/framework/function_test.py +++ b/tensorflow/python/framework/function_test.py @@ -1333,7 +1333,7 @@ class UnrollLSTMTest(test.TestCase): value=math_ops.matmul(xm, weights), num_or_size_splits=4, axis=1) new_c = math_ops.sigmoid(f_g) * cprev + math_ops.sigmoid( i_g) * math_ops.tanh(i_i) - new_c = clip_ops.clip_by_value(new_c, -50.0, 50.0) + new_c = math_ops.maximum(math_ops.minimum(new_c, 50.0), -50.0) new_m = math_ops.sigmoid(o_g) * math_ops.tanh(new_c) return new_m, new_c -- GitLab From 14e9c14ecdb9e9ddb283c5ec9cf27b3c5dbb900e Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 18 Dec 2017 18:58:42 +0000 Subject: [PATCH 292/906] Fix api_compatibility_test with `--update_goldens True` Signed-off-by: Yong Tang --- .../base_api/api_def_ClipByValue.pbtxt | 36 +++++++++++++++++++ .../python_api/api_def_ClipByValue.pbtxt | 4 +++ 2 files changed, 40 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_ClipByValue.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_ClipByValue.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_ClipByValue.pbtxt b/tensorflow/core/api_def/base_api/api_def_ClipByValue.pbtxt new file mode 100644 index 0000000000..803d8970ab --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_ClipByValue.pbtxt @@ -0,0 +1,36 @@ +op { + graph_op_name: "ClipByValue" + in_arg { + name: "t" + description: < ## Validate your installation @@ -657,14 +657,14 @@ This section documents the relevant values for Linux installations. CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0-cp27-none-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0-cp27-none-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -676,14 +676,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0-cp34-cp34m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0-cp34-cp34m-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -695,14 +695,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0-cp35-cp35m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0-cp35-cp35m-linux_x86_64.whl
 
@@ -714,14 +714,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0-cp36-cp36m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0-cp36-cp36m-linux_x86_64.whl
 
diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index fa6951a8f1..6f55e6a650 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -119,7 +119,7 @@ Take the following steps to install TensorFlow with Virtualenv: TensorFlow in the active Virtualenv is as follows:
 $ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0-py3-none-any.whl If you encounter installation problems, see [Common Installation Problems](#common-installation-problems). @@ -242,7 +242,7 @@ take the following steps: issue the following command:
 $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl 
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0-py3-none-any.whl If the preceding command fails, see [installation problems](#common-installation-problems). @@ -350,7 +350,7 @@ Take the following steps to install TensorFlow in an Anaconda environment: TensorFlow for Python 2.7:
 (targetDirectory)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0-py2-none-any.whl @@ -523,7 +523,7 @@ This section documents the relevant values for Mac OS installations.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0-py2-none-any.whl
 
@@ -531,5 +531,5 @@ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-a
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0-py3-none-any.whl
 
diff --git a/tensorflow/docs_src/install/install_sources.md b/tensorflow/docs_src/install/install_sources.md index 0454c172f8..73446663e9 100644 --- a/tensorflow/docs_src/install/install_sources.md +++ b/tensorflow/docs_src/install/install_sources.md @@ -359,10 +359,10 @@ Invoke `pip install` to install that pip package. The filename of the `.whl` file depends on your platform. For example, the following command will install the pip package -for TensorFlow 1.7.0rc1 on Linux: +for TensorFlow 1.7.0 on Linux:
-$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc1-py2-none-any.whl
+$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0-py2-none-any.whl
 
## Validate your installation @@ -459,8 +459,8 @@ Stack Overflow and specify the `tensorflow` tag. **Linux** - - + + @@ -480,7 +480,7 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc1GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.7.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
- + @@ -495,8 +495,8 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.7.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
- - + + diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 8b83257887..a486631621 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.7.0-rc1' +_VERSION = '1.7.0' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', -- GitLab From 95efef3271d67dd63ec2e397012a20d63d088668 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 11:18:45 -0700 Subject: [PATCH 347/906] Make ArithmeticOptimizer robust to failures of shape inference and individual stages. Get rid of graph annotation and use GraphProperties directly. PiperOrigin-RevId: 190801044 --- .../optimizers/arithmetic_optimizer.cc | 49 +++++++++++-------- .../optimizers/arithmetic_optimizer.h | 1 + .../optimizers/graph_optimizer_stage.cc | 4 ++ .../optimizers/graph_optimizer_stage.h | 3 ++ 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 5dd0b6f4b0..629872bf19 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -196,8 +196,6 @@ void SetSourceDataType(DataType dtype, NodeDef* node) { bool IsNumberType(DataType dtype) { return kNumberTypes.Contains(dtype); } -const char kOutputShapesAttr[] = "_output_shapes"; - // Shape is symbolically defined if it has a known rank, and each dimension is // defined, or is an unknown symbol (dim.size <= -2). bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape) { @@ -234,16 +232,19 @@ bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left, // Returns whether `reshape` is an identity op. The tensor that `reshape` // reshapes is the `output_pos`-th output of node `input`. bool ReshapeIsIdentity(const NodeDef& reshape, const NodeDef& input, - const int output_pos) { - if (!reshape.attr().count(kOutputShapesAttr) || - !input.attr().count(kOutputShapesAttr)) { + const int output_pos, + const GraphProperties& graph_properties) { + const std::vector& reshape_props = + graph_properties.GetOutputProperties(reshape.name()); + const std::vector& input_props = + graph_properties.GetOutputProperties(input.name()); + if (reshape_props.empty() || input_props.empty() || + input_props.size() <= output_pos) { return false; } - PartialTensorShape src_shape( - input.attr().at(kOutputShapesAttr).list().shape(output_pos)); - PartialTensorShape dst_shape( - reshape.attr().at(kOutputShapesAttr).list().shape(0)); + const PartialTensorShape& src_shape = input_props[output_pos].shape(); + const PartialTensorShape& dst_shape = reshape_props[0].shape(); if (src_shape.unknown_rank() || dst_shape.unknown_rank()) { return false; } @@ -1272,7 +1273,8 @@ string ArithmeticOptimizer::TrySimplifyAndReplaceUses( // outputs tensors of shape [M, N] while feeding it with tensors of shape // [M*N] (or worse). The reshape nodes are then necessary to update the // tensor metadata to the required shape. - if (ReshapeIsIdentity(*reshape, *input, output_pos)) { + if (can_use_shapes_ && + ReshapeIsIdentity(*reshape, *input, output_pos, *graph_properties_)) { return reshape->input(0); } } @@ -1586,11 +1588,11 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps() { std::vector> stages; - if (options_.combine_add_to_addn) { + if (options_.combine_add_to_addn && can_use_shapes_) { stages.push_back(std::unique_ptr( new AddOpsRewriteStage(ctx, ctx_ext))); } - if (options_.hoist_common_factor_out_of_aggregation) { + if (options_.hoist_common_factor_out_of_aggregation && can_use_shapes_) { stages.push_back(std::unique_ptr( new HoistCommonFactorOutOfAggregation(ctx, ctx_ext))); } @@ -1627,7 +1629,15 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps() { if (simplified_tensor.empty()) { for (auto& stage : stages) { if (stage->IsSupported(node)) { - TF_RETURN_IF_ERROR(stage->TrySimplify(node, &simplified_tensor)); + const Status stage_status = + stage->TrySimplify(node, &simplified_tensor); + // Each stage must be "error safe" (just like exception safe). In + // case of any error it must leave optimized graph unmodified. + if (!stage_status.ok()) { + LOG(WARNING) << "Failed to run arithmetic optimizer stage " + << stage->stage_name() + << ". Error: " << stage_status.error_message(); + } if (!simplified_tensor.empty()) { break; } @@ -1694,19 +1704,16 @@ Status ArithmeticOptimizer::Optimize(Cluster* /*cluster*/, &frame_map_, &num_frames)); // Shapes are only needed in aggressive mode. graph_properties_.reset(new GraphProperties(item)); - TF_RETURN_IF_ERROR(graph_properties_->InferStatically(false)); - // TODO(ezhulenev): Use GraphProperties to lookup tensor shapes directly - TF_RETURN_IF_ERROR(graph_properties_->AnnotateOutputShapes(optimized_graph_)); + const Status status = graph_properties_->InferStatically(false); + can_use_shapes_ = status.ok(); + if (!can_use_shapes_) { + LOG(WARNING) << "Shape inference failed."; + } // Perform the optimizations. DedupComputations(); TF_RETURN_IF_ERROR(SimplifyArithmeticOps()); - // Clear output shapes. - for (int i = 0; i < optimized_graph->node_size(); ++i) { - optimized_graph_->mutable_node(i)->mutable_attr()->erase(kOutputShapesAttr); - } - return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h index 965f0e9ea2..cdeed0554e 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h @@ -126,6 +126,7 @@ class ArithmeticOptimizer : public GraphOptimizer { RewriterConfig::Toggle opt_level_; ArithmeticOptimizerOptions options_; + bool can_use_shapes_ = false; bool fetch_nodes_known_ = false; std::unordered_set nodes_to_preserve_; std::unique_ptr node_map_; diff --git a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc index 7044705ade..1ea57f7b4f 100644 --- a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc +++ b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc @@ -42,6 +42,10 @@ Status GetInputNode(const GraphOptimizerContext& ctx, const string& input, Status GetTensorProperties(const GraphOptimizerContext& ctx, const string& tensor, OpInfo::TensorProperties* properties) { + if (ctx.graph_properties == nullptr) { + return errors::InvalidArgument("Graph properties are unknown."); + } + int port; string tensor_node_name = ParseNodeName(tensor, &port); if (port < 0) { diff --git a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h index be95c00d2d..c7af82abbb 100644 --- a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h +++ b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h @@ -117,6 +117,9 @@ class GraphOptimizerStage { : optimizer_name_(optimizer_name), stage_name_(stage_name), ctx_(ctx) {} virtual ~GraphOptimizerStage() = default; + const string& stage_name() const { return stage_name_; } + const string& optimizer_name() const { return optimizer_name_; } + // Check if we should try to simplify node. Returning true doesn't // guarantee that node will be simplified. // -- GitLab From 71b917851b8fcd36481306d225fa478e9e6f7b83 Mon Sep 17 00:00:00 2001 From: Chris Ying Date: Wed, 28 Mar 2018 11:22:28 -0700 Subject: [PATCH 348/906] Fix TPUClusterResolver tpu parameter for profiler tool. PiperOrigin-RevId: 190801968 --- .../contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py b/tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py index a730d6142d..0b78cf8695 100644 --- a/tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py +++ b/tensorflow/contrib/tpu/profiler/pip_package/cloud_tpu_profiler/main.py @@ -76,7 +76,7 @@ def main(unused_argv=None): else: tpu_cluster_resolver = ( tf.contrib.cluster_resolver.TPUClusterResolver( - tpu_names=[FLAGS.tpu_name], + [FLAGS.tpu_name], zone=FLAGS.tpu_zone, project=FLAGS.gcp_project)) service_addr = tpu_cluster_resolver.get_master() -- GitLab From b8384bbe0325c5b1c20838f9e6fd494e78e299dc Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 11:24:01 -0700 Subject: [PATCH 349/906] Updating tests in constant_folding_test.cc so that it evaluates the optimized and original graph and checks whether the output tensors produced by them are the same. PiperOrigin-RevId: 190802264 --- .../optimizers/constant_folding_test.cc | 62 +++++++++++-------- .../core/grappler/utils/grappler_test.cc | 5 ++ .../core/grappler/utils/grappler_test.h | 3 + 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index 85f877883c..e0ff9b17b1 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -107,8 +107,8 @@ TEST_F(ConstantFoldingTest, SimpleFolding) { EXPECT_EQ("Const", node_d.op()); std::vector fetch = {"d"}; - auto tensors_expected = EvaluateNodes(item.graph, fetch, {}); - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors_expected = EvaluateNodes(item.graph, fetch); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(1, tensors_expected.size()); EXPECT_EQ(1, tensors.size()); test::ExpectTensorEqual(tensors_expected[0], tensors[0]); @@ -193,10 +193,10 @@ TEST_F(ConstantFoldingTest, AddTree) { // Check that the result nodes have the expected value. std::vector fetch = {"c3", "c20"}; - auto tensor_expected = EvaluateNodes(item.graph, fetch, {}); + auto tensor_expected = EvaluateNodes(item.graph, fetch); EXPECT_EQ(fetch.size(), tensor_expected.size()); fetch = {"add_child", "mul_child"}; - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(fetch.size(), tensors.size()); for (int i = 0; i < fetch.size(); i++) { test::ExpectTensorEqual(tensor_expected[i], tensors[i]); @@ -436,10 +436,10 @@ TEST_F(ConstantFoldingTest, StrengthReduce_Reciprocal) { // Check that the reciprocals have the expected value. std::vector fetch = {"cf_half"}; - auto tensor_expected = EvaluateNodes(item.graph, fetch, {}); + auto tensor_expected = EvaluateNodes(item.graph, fetch); EXPECT_EQ(fetch.size(), tensor_expected.size()); fetch = {"ConstantFolding/div_f_recip", "ConstantFolding/realdiv_recip"}; - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(fetch.size(), tensors.size()); for (int i = 0; i < fetch.size(); i++) { test::ExpectTensorEqual(tensor_expected[0], tensors[i]); @@ -647,8 +647,8 @@ TEST_F(ConstantFoldingTest, FoldingNodeWithTwoOutputs) { EXPECT_EQ("Const", new_d.op()); std::vector fetch = {"e", "f"}; - auto tensors_expected = EvaluateNodes(item.graph, fetch, {}); - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors_expected = EvaluateNodes(item.graph, fetch); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(fetch.size(), tensors_expected.size()); EXPECT_EQ(fetch.size(), tensors.size()); for (int i = 0; i < fetch.size(); i++) { @@ -671,7 +671,7 @@ TEST_F(ConstantFoldingTest, ControlDependencies) { GrapplerItem item; item.fetch.push_back("e"); TF_CHECK_OK(scope.ToGraphDef(&item.graph)); - auto tensors_expected = EvaluateNodes(item.graph, item.fetch, {}); + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); EXPECT_EQ(1, tensors_expected.size()); ConstantFolding optimizer(nullptr /* cpu_device */); GraphDef output; @@ -688,8 +688,8 @@ TEST_F(ConstantFoldingTest, ControlDependencies) { if (node.name() == "e") { EXPECT_EQ("Const", node.op()); ++found; - auto folded = EvaluateNodes(output, {"e"}, {}); - auto expected = EvaluateNodes(item.graph, {"e"}, {}); + auto folded = EvaluateNodes(output, {"e"}); + auto expected = EvaluateNodes(item.graph, {"e"}); EXPECT_EQ(1, expected.size()); EXPECT_EQ(1, folded.size()); test::ExpectTensorEqual(folded[0], expected[0]); @@ -699,7 +699,7 @@ TEST_F(ConstantFoldingTest, ControlDependencies) { } } EXPECT_EQ(1, found); - auto tensors = EvaluateNodes(output, item.fetch, {}); + auto tensors = EvaluateNodes(output, item.fetch); EXPECT_EQ(1, tensors.size()); test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } @@ -735,8 +735,8 @@ TEST_F(ConstantFoldingTest, ControlDependenciesEmptyFetch) { if (node.name() == "i1") { EXPECT_EQ("Const", node.op()); ++found; - auto folded = EvaluateNodes(output, {"i1"}, {}); - auto expected = EvaluateNodes(item.graph, {"i1"}, {}); + auto folded = EvaluateNodes(output, {"i1"}); + auto expected = EvaluateNodes(item.graph, {"i1"}); EXPECT_EQ(1, expected.size()); EXPECT_EQ(1, folded.size()); test::ExpectTensorEqual(folded[0], expected[0]); @@ -746,8 +746,8 @@ TEST_F(ConstantFoldingTest, ControlDependenciesEmptyFetch) { if (node.name() == "i2") { EXPECT_EQ("Const", node.op()); ++found; - auto folded = EvaluateNodes(output, {"i2"}, {}); - auto expected = EvaluateNodes(item.graph, {"i2"}, {}); + auto folded = EvaluateNodes(output, {"i2"}); + auto expected = EvaluateNodes(item.graph, {"i2"}); EXPECT_EQ(1, expected.size()); EXPECT_EQ(1, folded.size()); test::ExpectTensorEqual(folded[0], expected[0]); @@ -775,7 +775,8 @@ TEST_F(ConstantFoldingTest, ControlDependenciesDeduplicate) { GrapplerItem item; item.fetch.push_back("i2"); TF_CHECK_OK(scope.ToGraphDef(&item.graph)); - + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); ConstantFolding optimizer(nullptr /* cpu_device */); GraphDef output; Status status = optimizer.Optimize(nullptr, item, &output); @@ -794,6 +795,9 @@ TEST_F(ConstantFoldingTest, ControlDependenciesDeduplicate) { EXPECT_EQ("^p2", node.input(1)); } } + auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } TEST_F(ConstantFoldingTest, VariableNumberOfOutputs) { @@ -865,8 +869,8 @@ TEST_F(ConstantFoldingTest, VariableNumberOfOutputs) { } EXPECT_EQ(8, constant_folded); - auto expected = EvaluateNodes(item.graph, outputs, {}); - auto optimized = EvaluateNodes(output, outputs, {}); + auto expected = EvaluateNodes(item.graph, outputs); + auto optimized = EvaluateNodes(output, outputs); ASSERT_EQ(expected.size(), optimized.size()); for (int i = 0; i < expected.size(); ++i) { test::ExpectTensorEqual(expected[i], optimized[i]); @@ -1293,7 +1297,7 @@ TEST_F(ConstantFoldingTest, MergeNodes) { EXPECT_EQ(6, found_nodes); std::vector fetch = {"out1", "idx1"}; - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(2, tensors.size()); const Tensor& out_value = tensors[0]; EXPECT_EQ(3 * 5, out_value.NumElements()); @@ -1803,6 +1807,12 @@ TEST_F(ConstantFoldingTest, LargeConstant) { EXPECT_EQ(2, found); EXPECT_GT(1024 * 1024, output.ByteSizeLong()); + + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + EXPECT_EQ(1, tensors_expected.size()); + auto tensors = EvaluateNodes(output, item.fetch); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } TEST_F(ConstantFoldingTest, SwitchIdenticalInputs) { @@ -1948,8 +1958,8 @@ TEST_F(ConstantFoldingTest, PartialFolding_AssociativeAndCommutative) { } std::vector fetch = {"acc0"}; - auto tensors_expected = EvaluateNodes(item.graph, fetch, {}); - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors_expected = EvaluateNodes(item.graph, fetch); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(1, tensors_expected.size()); EXPECT_EQ(1, tensors.size()); test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); @@ -1983,7 +1993,7 @@ TEST_F(ConstantFoldingTest, PartialFolding_Concat) { item.fetch = {"concat0", "concat1", "concat2", "concat3", "concat4", "concat5", "concat6", "concat7", "concat8", "concat9"}; - auto tensors_expected = EvaluateNodes(item.graph, {"concat0"}, {}); + auto tensors_expected = EvaluateNodes(item.graph, {"concat0"}); EXPECT_EQ(1, tensors_expected.size()); ConstantFolding optimizer(nullptr /* cpu_device */); GraphDef output; @@ -2034,7 +2044,7 @@ TEST_F(ConstantFoldingTest, PartialFolding_Concat) { } } - auto tensors = EvaluateNodes(output, {"concat0"}, {}); + auto tensors = EvaluateNodes(output, {"concat0"}); EXPECT_EQ(1, tensors.size()); test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); } @@ -2132,8 +2142,8 @@ TEST_F(ConstantFoldingTest, TrivialPack) { } std::vector fetch = {"stack"}; - auto tensors_expected = EvaluateNodes(item.graph, fetch, {}); - auto tensors = EvaluateNodes(output, fetch, {}); + auto tensors_expected = EvaluateNodes(item.graph, fetch); + auto tensors = EvaluateNodes(output, fetch); EXPECT_EQ(1, tensors_expected.size()); EXPECT_EQ(1, tensors.size()); EXPECT_EQ(tensors_expected[0].shape(), tensors[0].shape()); diff --git a/tensorflow/core/grappler/utils/grappler_test.cc b/tensorflow/core/grappler/utils/grappler_test.cc index 5c96359867..910b0acaef 100644 --- a/tensorflow/core/grappler/utils/grappler_test.cc +++ b/tensorflow/core/grappler/utils/grappler_test.cc @@ -39,6 +39,11 @@ GrapplerTest::GrapplerTest() { cfg->set_debug_stripper(RewriterConfig::OFF); } +std::vector GrapplerTest::EvaluateNodes( + const GraphDef& graph, const std::vector& node_names) const { + return EvaluateNodes(graph, node_names, {}); +} + std::vector GrapplerTest::EvaluateNodes( const GraphDef& graph, const std::vector& node_names, const std::vector>& inputs) const { diff --git a/tensorflow/core/grappler/utils/grappler_test.h b/tensorflow/core/grappler/utils/grappler_test.h index 4b160e7f16..3bc7bea454 100644 --- a/tensorflow/core/grappler/utils/grappler_test.h +++ b/tensorflow/core/grappler/utils/grappler_test.h @@ -34,6 +34,9 @@ class GrapplerTest : public ::testing::Test { GrapplerTest(); protected: + std::vector EvaluateNodes( + const GraphDef& graph, const std::vector& node_names) const; + std::vector EvaluateNodes( const GraphDef& graph, const std::vector& node_names, const std::vector>& inputs) const; -- GitLab From cc9944abe196827bae38975d813ee3e428349dcb Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 11:34:32 -0700 Subject: [PATCH 350/906] In contrib/all_reduce raise a ValueError if the input tensors do not have fully-defined shapes. PiperOrigin-RevId: 190804146 --- tensorflow/contrib/all_reduce/python/all_reduce.py | 7 +++---- tensorflow/contrib/all_reduce/python/all_reduce_test.py | 6 ++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/all_reduce/python/all_reduce.py b/tensorflow/contrib/all_reduce/python/all_reduce.py index 6658f0d9c1..8add2aacff 100644 --- a/tensorflow/contrib/all_reduce/python/all_reduce.py +++ b/tensorflow/contrib/all_reduce/python/all_reduce.py @@ -38,16 +38,15 @@ def _flatten_tensors(tensors): shape: the original shape of each element of input tensors Raises: - ValueError: tensors are empty or non-isomorphic. + ValueError: tensors are empty or non-isomorphic or have unknown shape. """ if not tensors: raise ValueError("tensors cannot be empty") shape = tensors[0].shape for tensor in tensors: shape = shape.merge_with(tensor.shape) - if shape.ndims is None: - raise ValueError("At least one of the tensors in 'tensors' must have " - "statically known rank.") + if not shape.is_fully_defined(): + raise ValueError("Tensors must have statically known shape.") if len(shape) != 1: reshaped = [] for t in tensors: diff --git a/tensorflow/contrib/all_reduce/python/all_reduce_test.py b/tensorflow/contrib/all_reduce/python/all_reduce_test.py index 47bab0a367..b3f5d92259 100644 --- a/tensorflow/contrib/all_reduce/python/all_reduce_test.py +++ b/tensorflow/contrib/all_reduce/python/all_reduce_test.py @@ -36,6 +36,12 @@ from tensorflow.python.platform import tf_logging class AllReduceTest(test_util.TensorFlowTestCase): + def testFlattenTensorsShapesDefined(self): + x = array_ops.placeholder(types_pb2.DT_FLOAT, [None]) + with self.assertRaisesRegexp(ValueError, + "must have statically known shape"): + ar._flatten_tensors([x, x]) + def testRingPermutations(self): # 0 devices pred_by_c_d, rank_by_c_d = ar._ring_permutations(1, 0, []) -- GitLab From 70a51319f1d6e42f0d5eadbf65e941419974aac4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 11:48:28 -0700 Subject: [PATCH 351/906] Fixes to DepthwiseConv kernel PiperOrigin-RevId: 190806668 --- .../internal/optimized/depthwiseconv_uint8.h | 11 +++-- .../depthwiseconv_uint8_3x3_filter.h | 43 ++++++++++++++++++- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8.h b/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8.h index c71b070680..0f78e0f728 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8.h @@ -1694,12 +1694,11 @@ inline void DepthwiseConv(const uint8* input_data, const Dims<4>& input_dims, TFLITE_DCHECK(output_depth == input_depth * depth_multiplier); #ifdef __aarch64__ - // Call kernel optimized for depthwise convolutions using 3x3 filters, - // stride = 1, no padding, depth_multiplier = 1 and depth a multiple of 16. - if (filter_width == 3 && filter_height == 3 && depth_multiplier == 1 && - (stride_width == 1 || stride_width == 2) && - (stride_height == 1 || stride_height == 2) && pad_width == 0 && - pad_height == 0 && (input_depth % 16) == 0) { + // Call kernel optimized for depthwise convolutions using 3x3 filters if + // parameters are supported. + if (Fast3by3FilterKernelSupported(input_dims, filter_dims, stride_width, + stride_height, pad_width, pad_height, + depth_multiplier, output_dims)) { DepthwiseConv3by3FilterDepth16( input_data, input_dims, input_offset, filter_data, filter_dims, filter_offset, bias_data, bias_dims, stride_width, stride_height, diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h b/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h index 9dc76e7608..a349892076 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h @@ -440,6 +440,47 @@ struct ConvKernel3x3FilterDepth16<1, 1> { } }; +inline bool Fast3by3FilterKernelSupported(const Dims<4>& input_dims, + const Dims<4>& filter_dims, + int stride_width, int stride_height, + int pad_width, int pad_height, + int depth_multiplier, + const Dims<4>& output_dims) { + const int input_height = ArraySize(input_dims, 2); + const int input_width = ArraySize(input_dims, 1); + const int input_depth = ArraySize(input_dims, 0); + const int filter_height = ArraySize(filter_dims, 2); + const int filter_width = ArraySize(filter_dims, 1); + const int output_height = ArraySize(output_dims, 2); + const int output_width = ArraySize(output_dims, 1); + + bool supported = filter_width == 3 && filter_height == 3 && + depth_multiplier == 1 && + (stride_width == 1 || stride_width == 2) && + (stride_height == 1 || stride_height == 2) && + pad_width == 0 && pad_height == 0 && (input_depth % 16) == 0; + + if (!supported) { + return false; + } + + // Handle case where padding is zero but type is not kValid. This would + // require special boundary case handling that is not supported yet. + + const int out_x = output_width - 1; + const int out_y = output_height - 1; + + const int in_x_origin = (out_x * stride_width) - pad_width; + const int in_y_origin = (out_y * stride_height) - pad_height; + + const int in_x_end = in_x_origin + filter_width; + const int in_y_end = in_y_origin + filter_height; + + // Supported only if filter on the right and bottom boundary lies completely + // within the input. + return in_x_end <= input_width && in_y_end <= input_height; +} + inline void DepthwiseConv3by3FilterDepth16( const uint8* input_data, const Dims<4>& input_dims, int32 input_offset, const uint8* filter_data, const Dims<4>& filter_dims, int32 filter_offset, @@ -634,7 +675,7 @@ inline void DepthwiseConv3by3FilterDepth16( // Handle the rest of the right side. for (; out_x < output_width; out_x++) { // This code path can only be reached if we're handling >1 x outputs - // at a time or support padding. + // at a time or support kSame padding. } } -- GitLab From f9dbf697535f8262d2513ade20ada85431c323f3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 12:00:18 -0700 Subject: [PATCH 352/906] Reorder element wise operators across reshape operators. This allows batch-norm folding to work across reshape operators. PiperOrigin-RevId: 190808678 --- tensorflow/contrib/lite/toco/BUILD | 1 + .../graph_transformations.h | 1 + .../swap_elementwise_binary.cc | 175 ++++++++++++++++++ .../toco/graph_transformations/tests/BUILD | 11 ++ .../tests/swap_elementwise_binary_test.cc | 89 +++++++++ tensorflow/contrib/lite/toco/toco_tooling.cc | 1 + 6 files changed, 278 insertions(+) create mode 100644 tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc create mode 100644 tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc diff --git a/tensorflow/contrib/lite/toco/BUILD b/tensorflow/contrib/lite/toco/BUILD index 051fa8de3c..8ed3e0e14e 100644 --- a/tensorflow/contrib/lite/toco/BUILD +++ b/tensorflow/contrib/lite/toco/BUILD @@ -280,6 +280,7 @@ cc_library( "graph_transformations/resolve_tensorflow_switch.cc", "graph_transformations/resolve_tensorflow_tile.cc", "graph_transformations/resolve_transpose_attributes.cc", + "graph_transformations/swap_elementwise_binary.cc", "graph_transformations/unfuse_activation_functions.cc", "graph_transformations/unpartition_embedding_lookup.cc", "graph_transformations/unroll_batch_matmul.cc", diff --git a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h index 640afc7c74..1291825c8e 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h +++ b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h @@ -180,6 +180,7 @@ DECLARE_GRAPH_TRANSFORMATION(ResolveConstantStridedSlice) DECLARE_GRAPH_TRANSFORMATION(ResolveConstantFill) DECLARE_GRAPH_TRANSFORMATION(ResolveConstantGather) DECLARE_GRAPH_TRANSFORMATION(ResolveMultiplyByZero) +DECLARE_GRAPH_TRANSFORMATION(SwapElementwiseBinary) DECLARE_GRAPH_TRANSFORMATION(Dequantize) DECLARE_GRAPH_TRANSFORMATION(UnpartitionEmbeddingLookup) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc b/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc new file mode 100644 index 0000000000..ecbce58d16 --- /dev/null +++ b/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc @@ -0,0 +1,175 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include +#include +#include + +#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" +#include "tensorflow/contrib/lite/toco/model.h" +#include "tensorflow/contrib/lite/toco/runtime/types.h" +#include "tensorflow/contrib/lite/toco/tooling_util.h" +#include "tensorflow/core/platform/logging.h" + +namespace toco { + +namespace { + +bool ShapesAllowSwapping(const string& input_array_name, + const string& const_array_name, Model* model) { + const Array& input_array = model->GetOrCreateArray(input_array_name); + const Array& const_array = model->GetOrCreateArray(const_array_name); + // Wait until these shapes have been resolved. + if (!input_array.has_shape() || !const_array.has_shape()) { + return false; + } + + // Currently swapping is not handled for scalar const_array, though that could + // be done once there is a test model. + if (RequiredBufferSizeForShape(input_array.shape()) != + RequiredBufferSizeForShape(const_array.shape())) { + return false; + } + + return true; +} + +} // namespace + +// Swaps: +// Input +// \ +// (Reshape Op) Const +// \ / +// (Add/Sub/Mul/Div op) +// | +// Output +// +// To: +// +// Input Const +// \ / +// (Add/Sub/Mul/Div op) +// | +// (Reshape Op) +// | +// Output +// +// This can allow Add/Mul ops from batch normalization to be folded into an +// Input op from a FullyConnected layer. +bool SwapElementwiseBinary::Run(Model* model, std::size_t op_index) { + const auto element_wise_op_it = model->operators.begin() + op_index; + std::unique_ptr& element_wise_op = *element_wise_op_it; + DCHECK(element_wise_op); + + switch (element_wise_op->type) { + case OperatorType::kAdd: + case OperatorType::kSub: + case OperatorType::kMul: + case OperatorType::kDiv: + break; + default: + return false; + } + + int reshape_input = -1; + Operator* op = GetOpWithOutput(*model, element_wise_op->inputs[0]); + if (!op) { + return false; + } + + if (op->type == OperatorType::kTensorFlowReshape) { + reshape_input = 0; + } else { + op = GetOpWithOutput(*model, element_wise_op->inputs[1]); + if (!op || op->type != OperatorType::kTensorFlowReshape) { + return false; + } + reshape_input = 1; + } + + int const_input = (reshape_input == 0) ? 1 : 0; + const string& const_input_array = element_wise_op->inputs[const_input]; + if (!IsConstantParameterArray(*model, const_input_array)) { + return false; + } + + // Do not fold division if denominator is not constant. + if (element_wise_op->type != OperatorType::kDiv && const_input != 1) { + return false; + } + + const auto reshape_it = + FindOpWithOutput(*model, element_wise_op->inputs[reshape_input]); + // Note: we take copies of the tensor names here, instead of const-refs as we + // may overwrite the original names. + const string reshape_input_name = (*reshape_it)->inputs[0]; + const string intermediate_name = (*reshape_it)->outputs[0]; + const string element_wise_output_name = element_wise_op->outputs[0]; + + // Check the reshape op input and const op have their shapes resolved. + if (!ShapesAllowSwapping(reshape_input_name, const_input_array, model)) { + return false; + } + + int count_ops_consuming_output = CountOpsWithInput(*model, intermediate_name); + DCHECK_GE(count_ops_consuming_output, 1); + if (count_ops_consuming_output > 1) { + AddMessageF( + "Not exchanging element-wise function with %s because it is " + "consumed by more than 1 other operator", + LogName(**reshape_it)); + return false; + } + + // If the element_wise_op was originally producing an output_array we can't + // swap as otherwise the output array would change. It'd be nice to still be + // able to swap but if code is relying on the fetch names instead of array + // indices this won't work. + for (int i = 0; i < model->flags.output_arrays_size(); ++i) { + if (model->flags.output_arrays(i) == element_wise_op->outputs[0]) { + AddMessageF( + "Not exchanging activation function with %s to preserve output array " + "name %s", + LogName(**reshape_it), element_wise_op->outputs[0]); + return false; + } + } + + // Rewire by changing inputs, including all consumers. + // TODO(b/76086261): Replace with new utility function. + Operator* consumer = GetFirstOpWithInput(*model, element_wise_output_name); + while (consumer) { + for (int i = 0; i < consumer->inputs.size(); ++i) { + if (consumer->inputs[i] == element_wise_output_name) { + consumer->inputs[i] = intermediate_name; + } + } + consumer = GetFirstOpWithInput(*model, element_wise_output_name); + } + element_wise_op->inputs[reshape_input] = reshape_input_name; + (*reshape_it)->inputs[0] = element_wise_output_name; + + // Clear shapes; this will allow shape propagation to fix the sizes for us. + model->GetOrCreateArray(element_wise_output_name).clear_shape(); + + // Finally, swap operators. Note that this only works when there are no other + // direct descendents of the reshape operator. + element_wise_op.swap(*reshape_it); + + return true; +} + +} // namespace toco diff --git a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD index 2f94f9cd8a..b975cc996b 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD +++ b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD @@ -18,6 +18,17 @@ tf_cc_test( ], ) +tf_cc_test( + name = "swap_elementwise_binary_test", + srcs = ["swap_elementwise_binary_test.cc"], + deps = [ + "//tensorflow/contrib/lite/toco:graph_transformations", + "//tensorflow/contrib/lite/toco:model", + "//tensorflow/contrib/lite/toco:tooling_util", + "@com_google_googletest//:gtest_main", + ], +) + tf_cc_test( name = "lstm_utils_test", srcs = ["lstm_utils_test.cc"], diff --git a/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc b/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc new file mode 100644 index 0000000000..c3778017f3 --- /dev/null +++ b/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc @@ -0,0 +1,89 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include +#include +#include + +#include +#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" +#include "tensorflow/contrib/lite/toco/model.h" +#include "tensorflow/contrib/lite/toco/tooling_util.h" + +namespace toco { + +namespace { + +int ShapeCount(const std::vector& size) { + CHECK(size.size()); + int count = 1; + for (int dim : size) { + count *= dim; + } + return count; +} + +// Adds a new parameter array to the model. +void AddConstArray(const string& name, const float* data, + const std::vector& size, Model* model) { + Array& array = model->GetOrCreateArray(name); + array.data_type = ArrayDataType::kFloat; + Shape* shape = array.mutable_shape(); + *(shape->mutable_dims()) = size; + + auto& buffer = array.GetMutableBuffer(); + buffer.data.resize(ShapeCount(size)); + std::copy(data, data + ShapeCount(size), buffer.data.data()); +} + +} // namespace + +TEST(SwapElementwiseBinaryTest, SwapsReshape) { + Model model; + const float parameters[2][4] = {{0., 1., 2., 3.}, {10., 11., 12., 13.}}; + + AddConstArray("before_reshape", parameters[0], {2, 2}, &model); + AddConstArray("add_vector", parameters[1], {1, 4}, &model); + + auto reshape_op = absl::make_unique(); + reshape_op->shape = {1, 4}; + reshape_op->inputs = {"before_reshape"}; + reshape_op->outputs = {"after_reshape"}; + Array& reshape_array = model.GetOrCreateArray("after_reshape"); + *(reshape_array.mutable_shape()) = {1, 4}; + + auto add_op = absl::make_unique(); + add_op->inputs = {"after_reshape", "add_vector"}; + add_op->outputs = {"add"}; + Array& add_array = model.GetOrCreateArray("add"); + *(add_array.mutable_shape()) = {1, 4}; + + model.operators.push_back(std::move(reshape_op)); + model.operators.push_back(std::move(add_op)); + + auto transformation = absl::make_unique(); + ASSERT_TRUE(transformation->Run(&model, 1)); + + Operator* op = GetOpWithOutput(model, "add"); + ASSERT_NE(nullptr, op); + ASSERT_EQ(OperatorType::kAdd, op->type); + ASSERT_EQ(2, op->inputs.size()); + for (const string& input : op->inputs) { + EXPECT_TRUE(IsConstantParameterArray(model, input)) + << input << " is not const input"; + } +} + +} // namespace toco diff --git a/tensorflow/contrib/lite/toco/toco_tooling.cc b/tensorflow/contrib/lite/toco/toco_tooling.cc index 30dd6fab9e..41ea1481bc 100644 --- a/tensorflow/contrib/lite/toco/toco_tooling.cc +++ b/tensorflow/contrib/lite/toco/toco_tooling.cc @@ -90,6 +90,7 @@ void MakeGeneralGraphTransformationsSet( transformations->Add(new ResolveTensorFlowTile); transformations->Add(new ResolveTensorFlowConcat); transformations->Add(new ResolveMultiplyByZero); + transformations->Add(new SwapElementwiseBinary); transformations->Add(new IdentifyDilatedConv); transformations->Add(new IdentifyL2Normalization); transformations->Add(new IdentifyL2Pool); -- GitLab From 195be47024f5608b284c52239d006b756cbad0d5 Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Wed, 28 Mar 2018 12:06:33 -0700 Subject: [PATCH 353/906] Add some VLOGs to make it easier to see why things don't go through the fast path PiperOrigin-RevId: 190809906 --- tensorflow/python/eager/pywrap_tfe_src.cc | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc index 30ef6781ec..73482792d5 100644 --- a/tensorflow/python/eager/pywrap_tfe_src.cc +++ b/tensorflow/python/eager/pywrap_tfe_src.cc @@ -1406,15 +1406,32 @@ bool CheckInputsOk(PyObject* seq, int start_index, if (!op_def.input_arg(i).number_attr().empty() || !op_def.input_arg(i).type_list_attr().empty()) { // This item should be a seq input. - if (!PySequence_Check(item)) return false; + if (!PySequence_Check(item)) { + VLOG(1) << "Falling back to slow path for Op \"" << op_def.name() + << "\", Input \"" << op_def.input_arg(i).name() + << "\" since we expected a sequence, but got " + << item->ob_type->tp_name; + return false; + } for (Py_ssize_t j = 0; j < PySequence_Fast_GET_SIZE(item); j++) { PyObject* inner_item = PySequence_Fast_GET_ITEM(item, j); if (!EagerTensor_CheckExact(inner_item) && !CheckResourceVariable(inner_item)) { + VLOG(1) + << "Falling back to slow path for Op \"" << op_def.name() + << "\", Input \"" << op_def.input_arg(i).name() << "\", Index " + << j + << " since we expected an EagerTensor/ResourceVariable, but got " + << inner_item->ob_type->tp_name; return false; } } } else if (!EagerTensor_CheckExact(item) && !CheckResourceVariable(item)) { + VLOG(1) + << "Falling back to slow path for Op \"" << op_def.name() + << "\", Input \"" << op_def.input_arg(i).name() + << "\" since we expected an EagerTensor/ResourceVariable, but got " + << item->ob_type->tp_name; return false; } } @@ -1894,6 +1911,9 @@ PyObject* TFE_Py_FastPathExecute_C(PyObject*, PyObject* args) { py_attr_value, &attr_list_sizes, status); if (TF_GetCode(status) != TF_OK) { + VLOG(1) << "Falling back to slow path for Op \"" << op_def->name() + << "\" since we are unable to set the value for attr \"" + << attr.name() << "\" due to: " << TF_Message(status); RaiseFallbackException(TF_Message(status)); return nullptr; } -- GitLab From e0956b390aabaf8882dff600056db805f3fccbf6 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Wed, 28 Mar 2018 12:16:51 -0700 Subject: [PATCH 354/906] Don't access properties in case they're not present PiperOrigin-RevId: 190811935 --- tensorflow/core/grappler/optimizers/constant_folding.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc index 22ede19493..c3f8a1ce22 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding.cc @@ -1534,6 +1534,7 @@ Status ConstantFolding::SimplifyGraph(GraphDef* optimized_graph, // Remove Shuffle or Reverse op over scalar values. if (use_shape_info && + !properties->GetInputProperties(node->name()).empty() && (IsShuffle(*node) || IsReverse(*node) || IsTranspose(*node))) { const auto& shape = properties->GetInputProperties(node->name())[0].shape(); -- GitLab From 4ec02c23174b07540d190cec620347ee6f31a8d8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 12:18:24 -0700 Subject: [PATCH 355/906] [XLA] Redesign: add the rest of the XlaBuilder public methods. PiperOrigin-RevId: 190812260 --- .../xla/client/xla_client/xla_builder.cc | 107 +++++++++++++++++- .../xla/client/xla_client/xla_builder.h | 71 ++++++++++++ .../xla/client/xla_client/xla_computation.h | 2 + 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index 7d39701b10..1b94f9a4eb 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -128,6 +128,18 @@ StatusOr XlaBuilder::GetProgramShape() { return GetProgramShape(&root_id); } +XlaComputation XlaBuilder::BuildAndNoteError() { + DCHECK(parent_builder_ != nullptr); + auto build_status = Build(); + if (!build_status.ok()) { + parent_builder_->NoteError( + AddStatus(build_status.status(), + tensorflow::strings::StrCat("error from: ", name_))); + return {}; + } + return build_status.ConsumeValueOrDie(); +} + StatusOr XlaBuilder::Build() { if (!first_error_.ok()) { string backtrace; @@ -945,6 +957,99 @@ XlaOp XlaBuilder::Recv(const Shape& shape, const ChannelHandle& handle) { return UnimplementedOp(); } +StatusOr XlaBuilder::IsConstant(const XlaOp& operand, + int64 num_parameters) { + return Unimplemented("IsConstant is not implemented."); +} + +StatusOr> XlaBuilder::ComputeConstant( + const XlaOp& operand, const Layout* output_layout, + tensorflow::gtl::ArraySlice parameters) { + return Unimplemented("ComputeConstant is not implemented"); +} + +std::unique_ptr XlaBuilder::CreateSubBuilder( + const string& computation_name) { + auto sub_builder = MakeUnique(computation_name); + sub_builder->parent_builder_ = this; + sub_builder->die_immediately_on_error_ = this->die_immediately_on_error_; + return sub_builder; +} + +Status XlaBuilder::SetReturnValue(const XlaOp& operand) { + return Unimplemented("SetReturnValue is not implemented."); +} + +/* static */ ConvolutionDimensionNumbers +XlaBuilder::CreateDefaultConvDimensionNumbers(int num_spatial_dims) { + ConvolutionDimensionNumbers dimension_numbers; + dimension_numbers.set_input_batch_dimension(kConvBatchDimension); + dimension_numbers.set_input_feature_dimension(kConvFeatureDimension); + dimension_numbers.set_output_batch_dimension(kConvBatchDimension); + dimension_numbers.set_output_feature_dimension(kConvFeatureDimension); + dimension_numbers.set_kernel_output_feature_dimension( + kConvKernelOutputDimension); + dimension_numbers.set_kernel_input_feature_dimension( + kConvKernelInputDimension); + for (int i = 0; i < num_spatial_dims; ++i) { + dimension_numbers.add_input_spatial_dimensions(i + 2); + dimension_numbers.add_kernel_spatial_dimensions(i + 2); + dimension_numbers.add_output_spatial_dimensions(i + 2); + } + return dimension_numbers; +} + +/* static */ Status XlaBuilder::Validate( + const ConvolutionDimensionNumbers& dnum) { + if (dnum.input_spatial_dimensions_size() < 2) { + return FailedPrecondition("input spacial dimension < 2: %d", + dnum.input_spatial_dimensions_size()); + } + if (dnum.kernel_spatial_dimensions_size() < 2) { + return FailedPrecondition("kernel spacial dimension < 2: %d", + dnum.kernel_spatial_dimensions_size()); + } + if (dnum.output_spatial_dimensions_size() < 2) { + return FailedPrecondition("output spacial dimension < 2: %d", + dnum.output_spatial_dimensions_size()); + } + + if (std::set( + {dnum.input_batch_dimension(), dnum.input_feature_dimension(), + dnum.input_spatial_dimensions(0), dnum.input_spatial_dimensions(1)}) + .size() != 4) { + return FailedPrecondition( + "dimension numbers for the input are not unique: (%lld, %lld, %lld, " + "%lld)", + dnum.input_batch_dimension(), dnum.input_feature_dimension(), + dnum.input_spatial_dimensions(0), dnum.input_spatial_dimensions(1)); + } + if (std::set({dnum.kernel_output_feature_dimension(), + dnum.kernel_input_feature_dimension(), + dnum.kernel_spatial_dimensions(0), + dnum.kernel_spatial_dimensions(1)}) + .size() != 4) { + return FailedPrecondition( + "dimension numbers for the weight are not unique: (%lld, %lld, %lld, " + "%lld)", + dnum.kernel_output_feature_dimension(), + dnum.kernel_input_feature_dimension(), + dnum.kernel_spatial_dimensions(0), dnum.kernel_spatial_dimensions(1)); + } + if (std::set({dnum.output_batch_dimension(), + dnum.output_feature_dimension(), + dnum.output_spatial_dimensions(0), + dnum.output_spatial_dimensions(1)}) + .size() != 4) { + return FailedPrecondition( + "dimension numbers for the output are not unique: (%lld, %lld, %lld, " + "%lld)", + dnum.output_batch_dimension(), dnum.output_feature_dimension(), + dnum.output_spatial_dimensions(0), dnum.output_spatial_dimensions(1)); + } + return Status::OK(); +} + StatusOr XlaBuilder::AddInstruction( HloInstructionProto&& instr, HloOpcode opcode, tensorflow::gtl::ArraySlice operands) { @@ -986,7 +1091,7 @@ StatusOr XlaBuilder::LookUpInstruction( } XlaOp XlaBuilder::UnimplementedOp() { - NoteError(Unimplemented("Op not yet implemented")); + NoteError(Unimplemented("Op not implemented")); return {}; } diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h index c5c35159e0..f66feb93ce 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h @@ -335,6 +335,26 @@ class XlaBuilder { XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs, const DotDimensionNumbers& dimension_numbers); + // Default dimension numbers used for a 2D convolution. + static constexpr int64 kConvBatchDimension = 0; + static constexpr int64 kConvFeatureDimension = 1; + static constexpr int64 kConvFirstSpatialDimension = 2; + static constexpr int64 kConvSecondSpatialDimension = 3; + static constexpr int64 kConvKernelOutputDimension = 0; + static constexpr int64 kConvKernelInputDimension = 1; + static constexpr int64 kConvKernelFirstSpatialDimension = 2; + static constexpr int64 kConvKernelSecondSpatialDimension = 3; + + // Creates a default ConvolutionDimensionNumbers. For a 2D convolution, for + // the input operand {batch, feature, height, width} = {0, 1, 2, 3} and for + // the kernel operand + // {output_feature, input_feature, height, width} = {0, 1, 2, 3}. + static ConvolutionDimensionNumbers CreateDefaultConvDimensionNumbers( + int num_spatial_dims = 2); + + // Returns an error if the convolution dimension numbers have conflicts. + static Status Validate(const ConvolutionDimensionNumbers& dnum); + // Enqueues a convolution instruction onto the computation, which uses the // default convolution dimension numbers. XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs, @@ -711,10 +731,59 @@ class XlaBuilder { const XlaOp& grad_output, float epsilon, int64 feature_index); + // Computes the value of a constant indicated by a XlaOp using a non-optimized + // interpreter on the host. + // + // The operand must represent a constant value, which in this case + // means that it must not statically depend on any parameter of the + // computation that is being built other then the ones specified on the + // parameter list. The parameters in the list will be indexed by their + // parameter id property so the number of parameters specified should be at + // least as many as the largest used parameter index. + // + // `IsConstant` can be used to test whether a computation is a compile-time + // constant without evaluation it. `ComputeConstant` only succeeds for + // computations where `IsConstant` returns true. + // + // This functionality can be useful when translating a computation + // into XLA where something that looked dynamic is required by + // XLA to be specified as a constant. E.g. the source + // computation (outside of XLA) may include a dynamic + // computation of the shape of something and ComputeConstant lets + // you determine what the value of that computation is in the case + // where the value can be determined at compile time. + // + // If output_layout is non-null, then the output of the computation + // will be stored using that layout. + StatusOr> ComputeConstant( + const XlaOp& operand, const Layout* output_layout = nullptr, + tensorflow::gtl::ArraySlice parameters = {}); + + // Returns a new XlaBuilder whose resultant Computation is used only by this + // XlaBuilder. The sub-XlaBuilder has the same die_immediately_on_error + // behavior as the parent. + std::unique_ptr CreateSubBuilder(const string& computation_name); + + // Modifies the computation being built so that executions of it will return + // the value associated with operand, rather than the last expression enqueued + // on the XlaBuilder. Any subsequent operations added to the XlaBuilder will + // not have any effect unless SetReturnValue is called again. + Status SetReturnValue(const XlaOp& operand); + // Builds the computation with the requested operations, or returns a non-ok // status. StatusOr Build(); + // Builds the computation with the requested operations, or notes an error in + // the parent XlaBuilder and returns an empty computation if building failed. + // This function is intended to be used where the returned XlaComputation is + // only used by the parent XlaBuilder and hence further operation on the + // returned XlaComputation will simply be error'ed out if an error occurred + // while building this computation. If the built computation is to be used by + // a XlaBuilder other than the parent XlaBuilder then Build() should be used + // instead. + XlaComputation BuildAndNoteError(); + // Returns the first error that was encountered while building the // computation. When an error is encountered, by default we return a vacuous // XlaOp and inform the user of the error that occurred while @@ -814,6 +883,8 @@ class XlaBuilder { // Mode bit that indicates whether to die when a first error is encountered. bool die_immediately_on_error_ = false; + + XlaBuilder* parent_builder_{nullptr}; }; template diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.h b/tensorflow/compiler/xla/client/xla_client/xla_computation.h index 5b89747fdd..78e1e3c32c 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_computation.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_computation.h @@ -29,6 +29,8 @@ namespace xla { // TODO(b/74197823): Replace xla::Computation with this one. class XlaComputation { public: + XlaComputation() : unique_id_(-1) {} + XlaComputation(const XlaComputation&) = delete; XlaComputation& operator=(const XlaComputation&) = delete; -- GitLab From 7863645e0323d3b2ef034a6499ec6673f0cca761 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 12:19:27 -0700 Subject: [PATCH 356/906] When importing meta graphs under name scopes, the names of the created ops are prepended with the scopes. Since the saver_def of the meta graph does not contain this information, we need to pass it explicitly to Saver. PiperOrigin-RevId: 190812434 --- tensorflow/python/training/saver.py | 20 +++++++++++---- tensorflow/python/training/saver_test.py | 32 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/training/saver.py b/tensorflow/python/training/saver.py index ba0d038475..cec581d997 100644 --- a/tensorflow/python/training/saver.py +++ b/tensorflow/python/training/saver.py @@ -1924,12 +1924,22 @@ def import_meta_graph(meta_graph_or_file, clear_devices=False, else: meta_graph_def = meta_graph_or_file - meta_graph.import_scoped_meta_graph(meta_graph_def, - clear_devices=clear_devices, - import_scope=import_scope, - **kwargs) + imported_vars = meta_graph.import_scoped_meta_graph( + meta_graph_def, + clear_devices=clear_devices, + import_scope=import_scope, + **kwargs) + if meta_graph_def.HasField("saver_def"): - return Saver(saver_def=meta_graph_def.saver_def, name=import_scope) + # Infer the scope that is prepended by `import_scoped_meta_graph`. + scope = import_scope + var_names = list(imported_vars.keys()) + if var_names: + sample_key = var_names[0] + sample_var = imported_vars[sample_key] + scope = sample_var.name[:-len(sample_key)] + + return Saver(saver_def=meta_graph_def.saver_def, name=scope) else: if variables._all_saveable_objects(): # pylint: disable=protected-access # Return the default saver instance for all graph variables. diff --git a/tensorflow/python/training/saver_test.py b/tensorflow/python/training/saver_test.py index 7de778f298..d1c24b3930 100644 --- a/tensorflow/python/training/saver_test.py +++ b/tensorflow/python/training/saver_test.py @@ -2341,6 +2341,38 @@ class MetaGraphTest(test.TestCase): 10, size=[1, 10]) }) + def testImportIntoImplicitNamescope(self): + # Test that we can import a meta graph into an implicit namescope. + test_dir = self._get_test_dir("import_into_namescope") + filename = os.path.join(test_dir, "ckpt") + image = array_ops.placeholder(dtypes.float32, [None, 784], name="image") + label = array_ops.placeholder(dtypes.float32, [None, 10], name="label") + with session.Session() as sess: + weights = variables.Variable( + random_ops.random_uniform([784, 10]), name="weights") + bias = variables.Variable(array_ops.zeros([10]), name="bias") + logit = nn_ops.relu(math_ops.matmul(image, weights) + bias, name="logits") + nn_ops.softmax(logit, name="prediction") + cost = nn_ops.softmax_cross_entropy_with_logits(labels=label, + logits=logit, name="cost") + adam.AdamOptimizer().minimize(cost, name="optimize") + saver = saver_module.Saver() + sess.run(variables.global_variables_initializer()) + saver.save(sess, filename) + + graph = ops_lib.Graph() + with session.Session(graph=graph) as sess: + with ops_lib.name_scope("new_model"): + new_saver = saver_module.import_meta_graph( + filename + ".meta", graph=graph) + + new_saver.restore(sess, filename) + sess.run(["new_model/optimize"], { + "new_model/image:0": np.random.random([1, 784]), + "new_model/label:0": np.random.randint( + 10, size=[1, 10]) + }) + def testClearDevicesOnImport(self): # Test that we import a graph without its devices and run successfully. with ops_lib.Graph().as_default(): -- GitLab From f80486324807181614ac71367dbb9cf588aa2804 Mon Sep 17 00:00:00 2001 From: Noah Eisen Date: Wed, 28 Mar 2018 12:28:32 -0700 Subject: [PATCH 357/906] Upgrade gRPC version used in OSS Tensorflow PiperOrigin-RevId: 190813848 --- tensorflow/contrib/cmake/external/grpc.cmake | 2 +- tensorflow/tools/pip_package/BUILD | 1 + tensorflow/workspace.bzl | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tensorflow/contrib/cmake/external/grpc.cmake b/tensorflow/contrib/cmake/external/grpc.cmake index cc218e8ab8..abfc69243e 100644 --- a/tensorflow/contrib/cmake/external/grpc.cmake +++ b/tensorflow/contrib/cmake/external/grpc.cmake @@ -17,7 +17,7 @@ include (ExternalProject) set(GRPC_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/grpc/src/grpc/include) set(GRPC_URL https://github.com/grpc/grpc.git) set(GRPC_BUILD ${CMAKE_CURRENT_BINARY_DIR}/grpc/src/grpc) -set(GRPC_TAG 575bda39755b98d1f7099406bb57a6e3b2074874) +set(GRPC_TAG bd6bdf93279a39a8cd92978fd7c9d14eccd98fc2) if(WIN32) if(${CMAKE_GENERATOR} MATCHES "Visual Studio.*") diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index 16c47f7555..dd75eda231 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -113,6 +113,7 @@ filegroup( "@lmdb//:LICENSE", "@local_config_sycl//sycl:LICENSE.text", "@grpc//third_party/nanopb:LICENSE.txt", + "@grpc//third_party/address_sorting:LICENSE", "@nasm//:LICENSE", "@nsync//:LICENSE", "@pcre//:LICENCE", diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 206a5a3d99..9fcbfb664b 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -405,13 +405,14 @@ def tf_workspace(path_prefix="", tf_repo_name=""): tf_http_archive( name = "grpc", urls = [ - "https://mirror.bazel.build/github.com/grpc/grpc/archive/575bda39755b98d1f7099406bb57a6e3b2074874.tar.gz", - "https://github.com/grpc/grpc/archive/575bda39755b98d1f7099406bb57a6e3b2074874.tar.gz", + "https://mirror.bazel.build/github.com/grpc/grpc/archive/bd6bdf93279a39a8cd92978fd7c9d14eccd98fc2.tar.gz", + "https://github.com/grpc/grpc/archive/bd6bdf93279a39a8cd92978fd7c9d14eccd98fc2.tar.gz", ], - sha256 = "f08a5c8e265191b39cc74915b1bc1fd380d86cd0176c92b7cce30b6ac50514ad", - strip_prefix = "grpc-575bda39755b98d1f7099406bb57a6e3b2074874", + sha256 = "0a05bd355e4571b01d813dddffa38e57e689ac41b264dc9b1bd6ec66463ef5d6", + strip_prefix = "grpc-bd6bdf93279a39a8cd92978fd7c9d14eccd98fc2", ) + tf_http_archive( name = "linenoise", sha256 = "7f51f45887a3d31b4ce4fa5965210a5e64637ceac12720cfce7954d6a2e812f7", -- GitLab From 560ef036727c871bab57faa9942ccaff977ef88a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 12:29:32 -0700 Subject: [PATCH 358/906] Supports quantized reduce_mean in TF Lite. PiperOrigin-RevId: 190813997 --- .../internal/reference/reference_ops.h | 22 +++-- tensorflow/contrib/lite/kernels/mean.cc | 62 +++++++++++--- tensorflow/contrib/lite/kernels/mean_test.cc | 81 +++++++++++-------- .../graph_transformations/hardcode_min_max.cc | 1 + .../toco/graph_transformations/quantize.cc | 2 +- 5 files changed, 114 insertions(+), 54 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h index ce12fad95d..33d60afa26 100644 --- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h @@ -3183,19 +3183,20 @@ inline void Exp(const T* input_data, const size_t num_elements, } } -template -inline void Mean(T* input_data, const int* input_dims, const int input_num_dims, +template +inline bool Mean(T* input_data, const int* input_dims, const int input_num_dims, T* output_data, const int* output_dims, const int output_num_dims, const int* axis, const int num_axis_dimensions, bool keep_dims, int* temp_index, - int* resolved_axis) { + int* resolved_axis, U* temp_sum) { // resets output data. size_t num_outputs = 1; for (int idx = 0; idx < output_num_dims; ++idx) { num_outputs *= static_cast(output_dims[idx]); } for (size_t idx = 0; idx < num_outputs; ++idx) { - output_data[idx] = 0; + output_data[idx] = T(); + temp_sum[idx] = U(); } // resets temp index. for (int idx = 0; idx < input_num_dims; ++idx) { @@ -3228,19 +3229,24 @@ inline void Mean(T* input_data, const int* input_dims, const int input_num_dims, size_t output_offset = ReducedOutputOffset(input_num_dims, input_dims, temp_index, num_resolved_axis, resolved_axis); - output_data[output_offset] += input_data[input_offset]; + temp_sum[output_offset] += static_cast(input_data[input_offset]); } // takes average by num of elements added to get mean. size_t num_elements_in_axis = 1; for (int idx = 0; idx < num_resolved_axis; ++idx) { - num_elements_in_axis *= static_cast(input_dims[resolved_axis[idx]]); + size_t current = static_cast(input_dims[resolved_axis[idx]]); + if (current > (std::numeric_limits::max() / num_elements_in_axis)) { + return false; + } + num_elements_in_axis *= current; } if (num_elements_in_axis > 0) { for (size_t idx = 0; idx < num_outputs; ++idx) { - output_data[idx] = static_cast(static_cast(output_data[idx]) / - num_elements_in_axis); + output_data[idx] = + static_cast(temp_sum[idx] / static_cast(num_elements_in_axis)); } } + return true; } template diff --git a/tensorflow/contrib/lite/kernels/mean.cc b/tensorflow/contrib/lite/kernels/mean.cc index aff19581ea..047bdd1039 100644 --- a/tensorflow/contrib/lite/kernels/mean.cc +++ b/tensorflow/contrib/lite/kernels/mean.cc @@ -16,6 +16,7 @@ limitations under the License. #include #include "tensorflow/contrib/lite/builtin_op_data.h" #include "tensorflow/contrib/lite/context.h" +#include "tensorflow/contrib/lite/kernels/internal/quantization_util.h" #include "tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h" #include "tensorflow/contrib/lite/kernels/internal/tensor.h" #include "tensorflow/contrib/lite/kernels/kernel_util.h" @@ -48,7 +49,7 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { // Creates two temp tensors to store index and axis for internal // implementation only. auto* scratch_tensor_index = new int; - context->AddTensors(context, 2, scratch_tensor_index); + context->AddTensors(context, 3, scratch_tensor_index); return scratch_tensor_index; } @@ -64,6 +65,14 @@ TfLiteStatus ResizeTempAxis(TfLiteContext* context, MeanContext* op_context, return context->ResizeTensor(context, resolved_axis, axis_size); } +// Resizes the temp tensor that stores temp sum of reduced elements. +TfLiteStatus ResizeTempSum(TfLiteContext* context, MeanContext* op_context, + TfLiteTensor* temp_sum) { + TfLiteIntArray* size = TfLiteIntArrayCreate(1); + size->data[0] = static_cast(NumElements(op_context->output)); + return context->ResizeTensor(context, temp_sum, size); +} + // Resizes output array based on the input size and resolved axis. TfLiteStatus ResizeOutputTensor(TfLiteContext* context, MeanContext* op_context) { @@ -135,7 +144,7 @@ TfLiteStatus InitializeTemporaries(TfLiteContext* context, TfLiteNode* node, // Creates a temp index to iterate through input data. int* scratch_tensor_index = reinterpret_cast(node->user_data); TfLiteIntArrayFree(node->temporaries); - node->temporaries = TfLiteIntArrayCreate(2); + node->temporaries = TfLiteIntArrayCreate(3); node->temporaries->data[0] = *scratch_tensor_index; TfLiteTensor* scratch_tensor = &context->tensors[node->temporaries->data[0]]; scratch_tensor->type = kTfLiteInt32; @@ -149,6 +158,25 @@ TfLiteStatus InitializeTemporaries(TfLiteContext* context, TfLiteNode* node, node->temporaries->data[1] = *scratch_tensor_index + 1; TfLiteTensor* resolved_axis = &context->tensors[node->temporaries->data[1]]; resolved_axis->type = kTfLiteInt32; + // Creates a temp tensor to store temp sums when calculating mean. + node->temporaries->data[2] = *scratch_tensor_index + 2; + TfLiteTensor* temp_sum = &context->tensors[node->temporaries->data[2]]; + switch (op_context->input->type) { + case kTfLiteFloat32: + temp_sum->type = kTfLiteFloat32; + break; + case kTfLiteInt32: + temp_sum->type = kTfLiteInt64; + break; + case kTfLiteInt64: + temp_sum->type = kTfLiteInt64; + break; + case kTfLiteUInt8: + temp_sum->type = kTfLiteInt32; + break; + default: + return kTfLiteError; + } return kTfLiteOk; } @@ -160,16 +188,20 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, InitializeTemporaries(context, node, &op_context)); TfLiteTensor* resolved_axis = &context->tensors[node->temporaries->data[1]]; + TfLiteTensor* temp_sum = &context->tensors[node->temporaries->data[2]]; // Leaves work to Eval if axis is not constant; else resizes output. if (!IsConstantTensor(op_context.axis)) { SetTensorToDynamic(op_context.output); SetTensorToDynamic(resolved_axis); + SetTensorToDynamic(temp_sum); return kTfLiteOk; } resolved_axis->allocation_type = kTfLiteArenaRw; TF_LITE_ENSURE_OK(context, ResizeTempAxis(context, &op_context, resolved_axis)); - return ResizeOutputTensor(context, &op_context); + TF_LITE_ENSURE_OK(context, ResizeOutputTensor(context, &op_context)); + temp_sum->allocation_type = kTfLiteArenaRw; + return ResizeTempSum(context, &op_context, temp_sum); } template @@ -178,14 +210,16 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { int num_axis = static_cast(NumElements(op_context.axis)); TfLiteTensor* temp_index = &context->tensors[node->temporaries->data[0]]; TfLiteTensor* resolved_axis = &context->tensors[node->temporaries->data[1]]; + TfLiteTensor* temp_sum = &context->tensors[node->temporaries->data[2]]; // Resize the output tensor if the output tensor is dynamic. if (IsDynamicTensor(op_context.output)) { TF_LITE_ENSURE_OK(context, ResizeTempAxis(context, &op_context, resolved_axis)); TF_LITE_ENSURE_OK(context, ResizeOutputTensor(context, &op_context)); + TF_LITE_ENSURE_OK(context, ResizeTempSum(context, &op_context, temp_sum)); } -#define TF_LITE_MEAN(kernel_type, data_type) \ +#define TF_LITE_MEAN(kernel_type, data_type, temp_data_type) \ kernel_type::Mean<>( \ GetTensorData(op_context.input), \ op_context.input->dims->data, op_context.input->dims->size, \ @@ -193,21 +227,26 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { op_context.output->dims->data, op_context.output->dims->size, \ GetTensorData(op_context.axis), num_axis, \ op_context.params->keep_dims, GetTensorData(temp_index), \ - GetTensorData(resolved_axis)) + GetTensorData(resolved_axis), \ + GetTensorData(temp_sum)) if (kernel_type == kReference) { switch (op_context.input->type) { case kTfLiteFloat32: - TF_LITE_MEAN(reference_ops, float); + TF_LITE_ENSURE(context, TF_LITE_MEAN(reference_ops, float, float)); break; case kTfLiteInt32: - TF_LITE_MEAN(reference_ops, int); - break; - case kTfLiteUInt8: - TF_LITE_MEAN(reference_ops, uint8_t); + TF_LITE_ENSURE(context, TF_LITE_MEAN(reference_ops, int, int64_t)); break; case kTfLiteInt64: - TF_LITE_MEAN(reference_ops, int64_t); + TF_LITE_ENSURE(context, TF_LITE_MEAN(reference_ops, int64_t, int64_t)); + break; + case kTfLiteUInt8: + TF_LITE_ENSURE_EQ(context, op_context.input->params.scale, + op_context.output->params.scale); + TF_LITE_ENSURE_EQ(context, op_context.input->params.zero_point, + op_context.output->params.zero_point); + TF_LITE_ENSURE(context, TF_LITE_MEAN(reference_ops, uint8_t, int)); break; default: return kTfLiteError; @@ -216,7 +255,6 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { #undef TF_LITE_MEAN return kTfLiteOk; } - } // namespace mean TfLiteRegistration* Register_MEAN_REF() { diff --git a/tensorflow/contrib/lite/kernels/mean_test.cc b/tensorflow/contrib/lite/kernels/mean_test.cc index 2d6d4bc2da..79c9957f76 100644 --- a/tensorflow/contrib/lite/kernels/mean_test.cc +++ b/tensorflow/contrib/lite/kernels/mean_test.cc @@ -37,8 +37,15 @@ class BaseMeanOpModel : public SingleOpModel { return ExtractVector(output_); } + std::vector GetDequantizedOutput() { + return Dequantize(ExtractVector(output_), + GetScale(output_), GetZeroPoint(output_)); + } + std::vector GetOutputShape() { return GetTensorShape(output_); } + int Input() { return input_; } + protected: int input_; int axis_; @@ -142,56 +149,64 @@ TEST(DynamicFloatMeanOpTest, Scale) { EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({9.527}))); } +// for quantized Add, the error shouldn't exceed step +float GetTolerance(int min, int max) { return (max - min) / 255.0; } + TEST(ConstUint8MeanOpTest, NotKeepDims) { - std::initializer_list data = {1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24}; - MeanOpConstModel m({TensorType_UINT8, {4, 3, 2}}, {TensorType_UINT8, {2}}, - {4}, {1, 0, -3, -3}, false); - m.SetInput(data); + float kQuantizedTolerance = GetTolerance(-1.0, 1.0); + std::initializer_list data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; + MeanOpConstModel m({TensorType_UINT8, {1, 3, 2}, -1.0, 1.0}, + {TensorType_UINT8, {2}, -1.0, 1.0}, {1}, {1}, false); + m.QuantizeAndPopulate(m.Input(), data); m.Invoke(); - EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2})); - EXPECT_THAT(m.GetOutput(), ElementsAreArray({12, 13})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); + EXPECT_THAT(m.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( + {0.4, 0.4}, kQuantizedTolerance))); } TEST(ConstUint8MeanOpTest, KeepDims) { - std::initializer_list data = {1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24}; - MeanOpConstModel m({TensorType_UINT8, {4, 3, 2}}, {TensorType_UINT8, {3}}, - {2}, {0, 2}, true); - m.SetInput(data); + float kQuantizedTolerance = GetTolerance(-1.0, 1.0); + std::initializer_list data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; + MeanOpConstModel m({TensorType_UINT8, {3, 2}, -1.0, 1.0}, + {TensorType_UINT8, {3}, -1.0, 1.0}, {1}, {1}, true); + m.QuantizeAndPopulate(m.Input(), data); m.Invoke(); - EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3, 1})); - EXPECT_THAT(m.GetOutput(), ElementsAreArray({10, 12, 14})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1})); + EXPECT_THAT( + m.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear({0.3, 0.35, 0.55}, kQuantizedTolerance))); } TEST(DynamicUint8MeanOpTest, NotKeepDims) { - std::initializer_list data = {1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24}; - MeanOpDynamicModel m({TensorType_UINT8, {4, 3, 2}}, {TensorType_UINT8, {2}}, - {TensorType_INT32, {4}}, false); - std::initializer_list axis = {1, 0, -3, -3}; + float kQuantizedTolerance = GetTolerance(-5.0, 2.0); + std::initializer_list data = {1.3, -4.8, -3.6, 0.24}; + MeanOpDynamicModel m({TensorType_UINT8, {2, 2}, -5.0, 2.0}, + {TensorType_UINT8, {2}, -5.0, 2.0}, + {TensorType_INT32, {1}}, false); + std::initializer_list axis = {1}; m.SetAxis(axis); - m.SetInput(data); + m.QuantizeAndPopulate(m.Input(), data); m.Invoke(); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2})); - EXPECT_THAT(m.GetOutput(), ElementsAreArray({12, 13})); + EXPECT_THAT( + m.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear({-1.75, -1.68}, kQuantizedTolerance))); } TEST(DynamicUint8MeanOpTest, KeepDims) { - std::initializer_list data = {1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24}; - MeanOpDynamicModel m({TensorType_UINT8, {4, 3, 2}}, {TensorType_UINT8, {3}}, - {TensorType_INT32, {2}}, true); - std::initializer_list axis = {0, 2}; + float kQuantizedTolerance = GetTolerance(-10.0, 12.0); + std::initializer_list data = {11.14, -0.14, 7.423, 0.879}; + MeanOpDynamicModel m({TensorType_UINT8, {2, 2}, -10.0, 12.0}, + {TensorType_UINT8, {2}, -10.0, 12.0}, + {TensorType_INT32, {1}}, true); + std::initializer_list axis = {0}; m.SetAxis(axis); - m.SetInput(data); + m.QuantizeAndPopulate(m.Input(), data); m.Invoke(); - EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3, 1})); - EXPECT_THAT(m.GetOutput(), ElementsAreArray({10, 12, 14})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); + EXPECT_THAT( + m.GetDequantizedOutput(), + ElementsAreArray(ArrayFloatNear({9.2815, 0.3695}, kQuantizedTolerance))); } } // namespace diff --git a/tensorflow/contrib/lite/toco/graph_transformations/hardcode_min_max.cc b/tensorflow/contrib/lite/toco/graph_transformations/hardcode_min_max.cc index 5cc82da5d5..7c97ef0d31 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/hardcode_min_max.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/hardcode_min_max.cc @@ -332,6 +332,7 @@ bool HardcodeMinMax::Run(Model* model, std::size_t op_index) { case OperatorType::kPad: case OperatorType::kGather: case OperatorType::kTranspose: + case OperatorType::kMean: changed = HardcodeMinMaxFromFirstInput(model, op); break; diff --git a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc index 9679ea0a77..9fcc95e1fe 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc @@ -52,7 +52,7 @@ bool SupportsQuantization(const Operator& op) { type == OperatorType::kStridedSlice || type == OperatorType::kDepthToSpace || type == OperatorType::kLstmCell || type == OperatorType::kGather || - type == OperatorType::kTranspose; + type == OperatorType::kTranspose || type == OperatorType::kMean; } template -- GitLab From dcbc0f007da212ae123efdd9eb86a72208a849da Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Wed, 28 Mar 2018 15:59:04 -0400 Subject: [PATCH 359/906] Update api.py (#18049) Avoid overwriting existing namespace items that might replace the converted functions. --- tensorflow/contrib/autograph/impl/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/autograph/impl/api.py b/tensorflow/contrib/autograph/impl/api.py index 1c4fcaa622..dce994e50d 100644 --- a/tensorflow/contrib/autograph/impl/api.py +++ b/tensorflow/contrib/autograph/impl/api.py @@ -247,7 +247,10 @@ def to_graph(e, # The compiled code should see everything the entry function saw. # TODO(mdan): This might not work well if the call tree spans modules? if tf_inspect.isfunction(e): - compiled_node.__dict__.update(inspect_utils.getnamespace(e)) + for key, val in inspect_utils.getnamespace(e).items(): + # Avoid overwriting entities that have been transformed. + if key not in compiled_node.__dict__: + compiled_node.__dict__[key] = val compiled_fn = getattr(compiled_node, name) if verbose: -- GitLab From 52aeafdf04af9f95500067dc353fd80728032b63 Mon Sep 17 00:00:00 2001 From: ngc92 <7938269+ngc92@users.noreply.github.com> Date: Wed, 28 Mar 2018 21:59:25 +0200 Subject: [PATCH 360/906] documenting that init_op will not be run when loading from checkpoint (#18051) --- tensorflow/python/training/session_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/training/session_manager.py b/tensorflow/python/training/session_manager.py index 360e02fb44..a00ceb9021 100644 --- a/tensorflow/python/training/session_manager.py +++ b/tensorflow/python/training/session_manager.py @@ -229,10 +229,14 @@ class SessionManager(object): up to `max_wait_secs`, for recovery to succeed. If the model cannot be recovered successfully then it is initialized by - either running the provided `init_op`, or calling the provided `init_fn`. - The local_init_op is also run after init_op and init_fn, regardless of + running the `init_op` and calling `init_fn` if they are provided. + The `local_init_op` is also run after init_op and init_fn, regardless of whether the model was recovered successfully, but only if - ready_for_local_init_op passes. + `ready_for_local_init_op` passes. + + If the model is recovered from a checkpoint it is assumed that all + global variables have been initialized, in particular neither `init_op` + nor `init_fn` will be executed. It is an error if the model cannot be recovered and no `init_op` or `init_fn` or `local_init_op` are passed. -- GitLab From 23c9e506bba637d9528cdf0c3a18a4cb05135a3a Mon Sep 17 00:00:00 2001 From: Loo Rong Jie Date: Thu, 29 Mar 2018 04:03:55 +0800 Subject: [PATCH 361/906] Replace PLATFORM_WINDOWS to _MSC_VER as it only applies to MSVC (#18047) --- tensorflow/core/platform/cpu_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/platform/cpu_info.h b/tensorflow/core/platform/cpu_info.h index 331f3e5251..bb77650e26 100644 --- a/tensorflow/core/platform/cpu_info.h +++ b/tensorflow/core/platform/cpu_info.h @@ -18,7 +18,7 @@ limitations under the License. #include -#if defined(PLATFORM_WINDOWS) +#if defined(_MSC_VER) #include "tensorflow/core/platform/windows/cpu_info.h" #endif -- GitLab From 6708117d292e09f259a4c685f8ca4d81cd6a0bd9 Mon Sep 17 00:00:00 2001 From: shengfuintel Date: Wed, 28 Mar 2018 13:04:08 -0700 Subject: [PATCH 362/906] Fixed the bug in mkl_input_conversion to compare tensorflow shape instead of mkl shape (#18033) --- .../core/kernels/mkl_input_conversion_op.cc | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/tensorflow/core/kernels/mkl_input_conversion_op.cc b/tensorflow/core/kernels/mkl_input_conversion_op.cc index d91f7107c5..68d3e1c9ab 100644 --- a/tensorflow/core/kernels/mkl_input_conversion_op.cc +++ b/tensorflow/core/kernels/mkl_input_conversion_op.cc @@ -263,21 +263,18 @@ class MklInputConversionOp : public OpKernel { private: void Compute(OpKernelContext* context) override { - const Tensor& input_tensor_0 = MklGetInput(context, 0); + const int kInputIndex_0 = 0, kInputIndex_1 = 1; + const Tensor& input_tensor_0 = MklGetInput(context, kInputIndex_0); MklDnnShape input_shape_0; - GetMklShape(context, 0, &input_shape_0); + GetMklShape(context, kInputIndex_0, &input_shape_0); - const Tensor& input_tensor_1 = MklGetInput(context, 1); + const Tensor& input_tensor_1 = MklGetInput(context, kInputIndex_1); MklDnnShape input_shape_1; - GetMklShape(context, 1, &input_shape_1); - - bool tf_shapes_are_same = - context->input(0).shape() == context->input(1).shape(); + GetMklShape(context, kInputIndex_1, &input_shape_1); - VLOG(1) << "MklInputConversionOp: Input shapes are " - << (tf_shapes_are_same ? "*same*" : "*different*") << ": " - << context->input(0).shape().DebugString() << " and " - << context->input(1).shape().DebugString(); + VLOG(1) << "MklInputConversionOp: Input shapes are: " + << context->input(kInputIndex_0).shape().DebugString() << " and " + << context->input(kInputIndex_1).shape().DebugString(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // if both inputs are in TF format, just copy input tensors to output. @@ -285,15 +282,19 @@ class MklInputConversionOp : public OpKernel { VLOG(1) << "MklInputConversionOp: No conversion needed, " << "copying TF inputs to output"; - ForwardTfTensorInToOut(context, 0, 0); - ForwardTfTensorInToOut(context, 1, 1); + ForwardTfTensorInToOut(context, kInputIndex_0, kInputIndex_0); + ForwardTfTensorInToOut(context, kInputIndex_1, kInputIndex_1); return; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If both inputs are in MKL format if (input_shape_0.IsMklTensor() && input_shape_1.IsMklTensor()) { - if (tf_shapes_are_same) { + // It is safer to compare the original TensorFlow shapes than to compare + // Mkl shapes since element wise ops are forwarded to Eigen implementation. + TensorShape tf_shape0 = input_shape_0.GetTfShape(); + TensorShape tf_shape1 = input_shape_1.GetTfShape(); + if (tf_shape0 == tf_shape1) { auto input0_md = input_shape_0.GetMklLayout(); auto input1_md = input_shape_1.GetMklLayout(); @@ -302,8 +303,8 @@ class MklInputConversionOp : public OpKernel { VLOG(1) << "MklInputConversionOp: No conversion needed, " << "copying MKL inputs with identical shapes to output"; - ForwardMklTensorInToOut(context, 0, 0); - ForwardMklTensorInToOut(context, 1, 1); + ForwardMklTensorInToOut(context, kInputIndex_0, kInputIndex_0); + ForwardMklTensorInToOut(context, kInputIndex_1, kInputIndex_1); return; } else { VLOG(1) << "MklInputConversionOp: Shape is same, but format is " @@ -324,7 +325,7 @@ class MklInputConversionOp : public OpKernel { mkl_output_mkl_shape.SetMklLayout(&input1_md); // Create output Mkl tensor for index 0 - AllocateOutputSetMklShape(context, 0, &tensor_out, + AllocateOutputSetMklShape(context, kInputIndex_0, &tensor_out, input_tensor_0.shape(), mkl_output_mkl_shape); @@ -342,7 +343,7 @@ class MklInputConversionOp : public OpKernel { stream(stream::kind::eager).submit(net).wait(); // Input1 will be passed through - ForwardMklTensorInToOut(context, 1, 1); + ForwardMklTensorInToOut(context, kInputIndex_1, kInputIndex_1); return; } } @@ -361,11 +362,11 @@ class MklInputConversionOp : public OpKernel { << "converted MKL inputs to TF format"; MklToTfOp::ConvertMklToTf(this, context, data_format_str, - op_data_type, has_avx512f_, 0); + op_data_type, has_avx512f_, kInputIndex_0); MklToTfOp::ConvertMklToTf(this, context, data_format_str, - op_data_type, has_avx512f_, 1); - SetDummyMklShapeOutput(context, 0); - SetDummyMklShapeOutput(context, 1); + op_data_type, has_avx512f_, kInputIndex_1); + SetDummyMklShapeOutput(context, kInputIndex_0); + SetDummyMklShapeOutput(context, kInputIndex_1); return; } @@ -377,7 +378,6 @@ class MklInputConversionOp : public OpKernel { const Tensor* mkl_tensor; const MklDnnShape* mkl_shape; const Tensor* tf_tensor; - MklDnnShape* tf_mkl_shape; uint mkl_tensor_index; uint tf_tensor_index; if (input_shape_0.IsMklTensor() && !input_shape_1.IsMklTensor()) { @@ -385,14 +385,12 @@ class MklInputConversionOp : public OpKernel { mkl_shape = &input_shape_0; mkl_tensor_index = 0; tf_tensor = &input_tensor_1; - tf_mkl_shape = &input_shape_1; tf_tensor_index = 1; } else if (!input_shape_0.IsMklTensor() && input_shape_1.IsMklTensor()) { mkl_tensor = &input_tensor_1; mkl_shape = &input_shape_1; mkl_tensor_index = 1; tf_tensor = &input_tensor_0; - tf_mkl_shape = &input_shape_0; tf_tensor_index = 0; } else { CHECK(false) << "MklInputConversionOp: Unexpected combination of input " @@ -466,8 +464,8 @@ class MklInputConversionOp : public OpKernel { } VLOG(1) << "MklInputConversionOp: Shapes (output): " - << context->mutable_output(0)->shape().DebugString() << " and " - << context->mutable_output(1)->shape().DebugString(); + << context->mutable_output(kInputIndex_0)->shape().DebugString() << " and " + << context->mutable_output(kInputIndex_1)->shape().DebugString(); VLOG(1) << "MklInputConversion completed successfully."; } -- GitLab From bb4e724f429ae5c9afad3a343dc1f483ecde1f74 Mon Sep 17 00:00:00 2001 From: George Sterpu Date: Wed, 28 Mar 2018 22:05:28 +0200 Subject: [PATCH 363/906] small update ctc_ops docstring (#18046) --- tensorflow/python/ops/ctc_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/ctc_ops.py b/tensorflow/python/ops/ctc_ops.py index 4b57e2de79..908e793902 100644 --- a/tensorflow/python/ops/ctc_ops.py +++ b/tensorflow/python/ops/ctc_ops.py @@ -218,7 +218,7 @@ def ctc_greedy_decoder(inputs, sequence_length, merge_repeated=True): The rows store: `[batch, time]`. `decoded.values`: Values vector, size `(total_decoded_outputs)`. The vector stores the decoded classes. - `decoded.shape`: Shape vector, size `(2)`. + `decoded.dense_shape`: Shape vector, size `(2)`. The shape values are: `[batch_size, max_decoded_length]` neg_sum_logits: A `float` matrix `(batch_size x 1)` containing, for the sequence found, the negative of the sum of the greatest logit at each @@ -265,7 +265,7 @@ def ctc_beam_search_decoder(inputs, sequence_length, beam_width=100, The rows store: [batch, time]. `decoded[j].values`: Values vector, size `(total_decoded_outputs[j])`. The vector stores the decoded classes for beam j. - `decoded[j].shape`: Shape vector, size `(2)`. + `decoded[j].dense_shape`: Shape vector, size `(2)`. The shape values are: `[batch_size, max_decoded_length[j]]`. log_probability: A `float` matrix `(batch_size x top_paths)` containing sequence log-probabilities. -- GitLab From 480ac84aa8390e19a54bd2feef3a6069d959bb4e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 13:11:12 -0700 Subject: [PATCH 364/906] Add op cost model for MaxPool, AvgPool, FusedBatchNorm, their grad ops, and ReluGrad. PiperOrigin-RevId: 190821116 --- .../grappler/costs/op_level_cost_estimator.cc | 306 +++++++++++++- .../grappler/costs/op_level_cost_estimator.h | 14 +- .../costs/op_level_cost_estimator_test.cc | 391 ++++++++++++++++++ 3 files changed, 709 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc index 905cc2a215..0f6307cfdf 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc @@ -50,6 +50,12 @@ constexpr char kPreventGradient[] = "PreventGradient"; constexpr char kGather[] = "Gather"; constexpr char kGatherV2[] = "GatherV2"; constexpr char kSlice[] = "Slice"; +constexpr char kMaxPool[] = "MaxPool"; +constexpr char kMaxPoolGrad[] = "MaxPoolGrad"; +constexpr char kAvgPool[] = "AvgPool"; +constexpr char kAvgPoolGrad[] = "AvgPoolGrad"; +constexpr char kFusedBatchNorm[] = "FusedBatchNorm"; +constexpr char kFusedBatchNormGrad[] = "FusedBatchNormGrad"; static const Costs::Duration kMinComputeTime(1); @@ -71,14 +77,39 @@ Padding GetPadding(const OpInfo& op_features) { return Padding::SAME; // Default padding. } +bool IsTraining(const OpInfo& op_info) { + if (op_info.attr().find("is_training") != op_info.attr().end() && + op_info.attr().at("is_training").b()) { + return true; + } + return false; +} + +// TODO(dyoon): support non-4D tensors in the c ost functions of convolution +// related ops (Conv, Pool, BatchNorm, and their backprops) and the related +// helper functions. std::vector GetStrides(const OpInfo& op_features) { if (op_features.attr().find("strides") != op_features.attr().end()) { const auto strides = op_features.attr().at("strides").list().i(); + CHECK(strides.size() == 4) << "Attr strides is not a length-4 vector: " + << op_features.DebugString(); return {strides[0], strides[1], strides[2], strides[3]}; } return {1, 1, 1, 1}; } +std::vector GetKernelSize(const OpInfo& op_info) { + if (op_info.attr().find("ksize") != op_info.attr().end()) { + const auto ksize = op_info.attr().at("ksize").list().i(); + CHECK(ksize.size() == 4) + << "Attr ksize is not a length-4 vector: " << op_info.DebugString(); + return {ksize[0], ksize[1], ksize[2], ksize[3]}; + } + // Note that FusedBatchNorm doesn't have ksize attr, but GetKernelSize returns + // {1, 1, 1, 1} in that case. + return {1, 1, 1, 1}; +} + int64 GetOutputSize(const int64 input, const int64 filter, const int64 stride, const Padding& padding) { // Logic for calculating output shape is from GetWindowedOutputSizeVerbose() @@ -193,7 +224,15 @@ OpLevelCostEstimator::OpLevelCostEstimator() { {kRank, wrap(&OpLevelCostEstimator::PredictMetadata)}, {kShape, wrap(&OpLevelCostEstimator::PredictMetadata)}, - {kSize, wrap(&OpLevelCostEstimator::PredictMetadata)}}; + {kSize, wrap(&OpLevelCostEstimator::PredictMetadata)}, + {kMaxPool, wrap(&OpLevelCostEstimator::PredictMaxPool)}, + {kMaxPoolGrad, wrap(&OpLevelCostEstimator::PredictMaxPoolGrad)}, + {kAvgPool, wrap(&OpLevelCostEstimator::PredictAvgPool)}, + {kAvgPoolGrad, wrap(&OpLevelCostEstimator::PredictAvgPoolGrad)}, + {kFusedBatchNorm, wrap(&OpLevelCostEstimator::PredictFusedBatchNorm)}, + {kFusedBatchNormGrad, + wrap(&OpLevelCostEstimator::PredictFusedBatchNormGrad)}, + }; #define EIGEN_COST(X) Eigen::internal::functor_traits::Cost @@ -258,6 +297,7 @@ OpLevelCostEstimator::OpLevelCostEstimator() { {"QuantizedAdd", EIGEN_COST(scalar_sum_op)}, {"QuantizedMul", EIGEN_COST(scalar_product_op)}, {"RealDiv", EIGEN_COST(scalar_quotient_op)}, + {"ReluGrad", EIGEN_COST(scalar_max_op)}, {"SquareDifference", 1}, {"Sub", EIGEN_COST(scalar_difference_op)}, {"TruncateDiv", EIGEN_COST(scalar_quotient_op)}, @@ -1044,5 +1084,269 @@ Costs OpLevelCostEstimator::PredictGatherOrSlice( return costs; } +/* static */ +OpLevelCostEstimator::ConvolutionDimensions +OpLevelCostEstimator::OpDimensionsFromInputs( + const TensorShapeProto& original_image_shape, const OpInfo& op_info, + bool* found_unknown_shapes) { + VLOG(2) << "op features: " << op_info.DebugString(); + VLOG(2) << "Original image shape: " << original_image_shape.DebugString(); + auto image_shape = + MaybeGetMinimumShape(original_image_shape, 4, found_unknown_shapes); + VLOG(2) << "Image shape: " << image_shape.DebugString(); + + int x_index, y_index, channel_index; + const string& data_format = GetDataFormat(op_info); + if (data_format == "NCHW") { + x_index = 2; + y_index = 3; + channel_index = 1; + } else { + x_index = 1; + y_index = 2; + channel_index = 3; + } + int64 batch = image_shape.dim(0).size(); + int64 ix = image_shape.dim(x_index).size(); + int64 iy = image_shape.dim(y_index).size(); + int64 iz = image_shape.dim(channel_index).size(); + + // Note that FusedBatchNorm doesn't have ksize attr, but GetKernelSize returns + // {1, 1, 1, 1} in that case. + std::vector ksize = GetKernelSize(op_info); + int64 kx = ksize[x_index]; + int64 ky = ksize[y_index]; + + std::vector strides = GetStrides(op_info); + int64 sx = strides[x_index]; + int64 sy = strides[y_index]; + const auto padding = GetPadding(op_info); + + int64 ox = GetOutputSize(ix, kx, sx, padding); + int64 oy = GetOutputSize(iy, ky, sy, padding); + int64 oz = iz; + + OpLevelCostEstimator::ConvolutionDimensions conv_dims = { + batch, ix, iy, iz, kx, ky, oz, ox, oy, sx, sy, padding}; + return conv_dims; +} + +Costs OpLevelCostEstimator::PredictMaxPool(const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // x: op_info.inputs(0) + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + // kx * ky - 1 comparisons per output (kx * xy > 1) + // or 1 copy per output (kx * k1 = 1). + int per_output_ops = dims.kx * dims.ky == 1 ? 1 : dims.kx * dims.ky - 1; + int64 ops = dims.batch * dims.ox * dims.oy * dims.oz * per_output_ops; + + double total_input_size = 0; + if (dims.ky >= dims.sy) { + total_input_size = + CalculateTensorSize(op_info.inputs(0), &found_unknown_shapes); + } else { // dims.ky < dims.sy + // Vertical stride is larger than vertical kernel; assuming row-major + // format, skip unnecessary rows (or read every kx rows per sy rows, as the + // others are not used for output). + const auto data_size = DataTypeSize(BaseType(op_info.inputs(0).dtype())); + total_input_size = + data_size * dims.batch * dims.ix * dims.ky * dims.oy * dims.iz; + } + const double total_output_size = + CalculateOutputSize(op_info, &found_unknown_shapes); + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size, op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + +Costs OpLevelCostEstimator::PredictMaxPoolGrad( + const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // x: op_info.inputs(0) + // y: op_info.inputs(1) + // y_grad: op_info.inputs(2) + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + + int64 ops = 0; + if (dims.kx == 1 && dims.ky == 1) { + // 1x1 window. No need to know which input was max. + ops = dims.batch * dims.ix * dims.iy * dims.iz; + } else if (dims.kx <= dims.sx && dims.ky <= dims.sy) { + // Non-overlapping window: re-run maxpool, then assign zero or y_grad. + ops = dims.batch * dims.iz * + (dims.ox * dims.oy * (dims.kx * dims.ky - 1) + dims.ix * dims.iy); + } else { + // Overlapping window: initialize with zeros, re-run maxpool, then + // accumulate y_gad to proper x_grad locations. + ops = dims.batch * dims.iz * + (dims.ox * dims.oy * (dims.kx * dims.ky - 1) + dims.ix * dims.iy * 2); + } + + // Just read x and y_grad; no need to read y as we assume MaxPoolGrad re-run + // MaxPool internally. + double total_input_size = + CalculateTensorSize(op_info.inputs(0), &found_unknown_shapes); + total_input_size += + CalculateTensorSize(op_info.inputs(2), &found_unknown_shapes); + // Write x_grad; size equal to x. + const double total_output_size = + CalculateTensorSize(op_info.inputs(0), &found_unknown_shapes); + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size, op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + +Costs OpLevelCostEstimator::PredictAvgPool(const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // x: op_info.inputs(0) + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + + // kx * ky - 1 additions and 1 multiplication per output. + int64 ops = dims.batch * dims.ox * dims.oy * dims.oz * dims.kx * dims.ky; + + double total_input_size = 0; + if (dims.ky >= dims.sy) { + total_input_size = + CalculateTensorSize(op_info.inputs(0), &found_unknown_shapes); + } else { // dims.ky < dims.sy + // vertical stride is larger than vertical kernel; assuming row-major + // format, skip unnecessary rows (or read every kx rows per sy rows, as the + // others are not used for output). + const auto data_size = DataTypeSize(BaseType(op_info.inputs(0).dtype())); + total_input_size = + data_size * dims.batch * dims.ix * dims.ky * dims.oy * dims.iz; + } + const double total_output_size = + CalculateOutputSize(op_info, &found_unknown_shapes); + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size, op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + +Costs OpLevelCostEstimator::PredictAvgPoolGrad( + const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // x: op_info.inputs(0) + // y_grad: op_info.inputs(1) + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + + int64 ops = 0; + if (dims.kx <= dims.sx && dims.ky <= dims.sy) { + // Non-overlapping window. + ops = dims.batch * dims.iz * (dims.ix * dims.iy + dims.ox * dims.oy); + } else { + // Overlapping window. + ops = dims.batch * dims.iz * + (dims.ix * dims.iy + dims.ox * dims.oy * (dims.kx * dims.ky + 1)); + } + + const double total_input_size = + CalculateInputSize(op_info, &found_unknown_shapes); + const double total_output_size = + CalculateOutputSize(op_info, &found_unknown_shapes); + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size, op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + +Costs OpLevelCostEstimator::PredictFusedBatchNorm( + const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // x: op_info.inputs(0) + // scale: op_info.inputs(1) + // offset: op_info.inputs(2) + // mean: op_info.inputs(3) --> only for inference + // variance: op_info.inputs(4) --> only for inference + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(0).shape(), op_info, &found_unknown_shapes); + const bool is_training = IsTraining(op_info); + + int64 ops = 0; + const auto rsqrt_cost = Eigen::internal::functor_traits< + Eigen::internal::scalar_rsqrt_op>::Cost; + if (is_training) { + ops = dims.iz * (dims.batch * dims.ix * dims.iy * 4 + 6 + rsqrt_cost); + } else { + ops = dims.batch * dims.ix * dims.iy * dims.iz * 2; + } + + const double size_nhwc = + CalculateTensorSize(op_info.inputs(0), &found_unknown_shapes); + const double size_c = + CalculateTensorSize(op_info.inputs(1), &found_unknown_shapes); + double total_input_size = 0.0; + double total_internal_read_size = 0.0; + double total_output_size = 0.0; + if (is_training) { + total_input_size = size_nhwc + size_c * 2; + total_output_size = size_nhwc + size_c * 4; + total_internal_read_size = size_nhwc; + } else { + total_input_size = size_nhwc + size_c * 4; + total_output_size = size_nhwc; + } + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size + total_internal_read_size, + op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + +Costs OpLevelCostEstimator::PredictFusedBatchNormGrad( + const OpContext& op_context) const { + bool found_unknown_shapes = false; + const auto& op_info = op_context.op_info; + // y_backprop: op_info.inputs(0) + // x: op_info.inputs(1) + // scale: op_info.inputs(2) + // mean: op_info.inputs(3) + // variance or inverse of variance: op_info.inputs(4) + ConvolutionDimensions dims = OpDimensionsFromInputs( + op_info.inputs(1).shape(), op_info, &found_unknown_shapes); + + int64 ops = 0; + const auto rsqrt_cost = Eigen::internal::functor_traits< + Eigen::internal::scalar_rsqrt_op>::Cost; + ops = dims.iz * (dims.batch * dims.ix * dims.iy * 11 + 5 + rsqrt_cost); + + const double size_nhwc = + CalculateTensorSize(op_info.inputs(1), &found_unknown_shapes); + const double size_c = + CalculateTensorSize(op_info.inputs(2), &found_unknown_shapes); + double total_input_size = size_nhwc * 2 + size_c * 2; + double total_internal_read_size = size_nhwc; + double total_output_size = size_nhwc * 1 + size_c * 2; + + Costs costs = PredictOpCountBasedCost( + ops, total_input_size + total_output_size + total_internal_read_size, + op_info); + costs.inaccurate = found_unknown_shapes; + costs.max_memory = total_output_size; + return costs; +} + } // end namespace grappler } // end namespace tensorflow diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.h b/tensorflow/core/grappler/costs/op_level_cost_estimator.h index 1b3babb206..fcbecbb6dc 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.h +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.h @@ -145,6 +145,12 @@ class OpLevelCostEstimator { Costs PredictBatchMatMul(const OpContext& op_context) const; Costs PredictMetadata(const OpContext& op_context) const; Costs PredictGatherOrSlice(const OpContext& op_context) const; + Costs PredictMaxPool(const OpContext& op_context) const; + Costs PredictMaxPoolGrad(const OpContext& op_context) const; + Costs PredictAvgPool(const OpContext& op_context) const; + Costs PredictAvgPoolGrad(const OpContext& op_context) const; + Costs PredictFusedBatchNorm(const OpContext& op_context) const; + Costs PredictFusedBatchNormGrad(const OpContext& op_context) const; // Utility function for safe division. Returns 0 // if rhs is 0 or negative. @@ -156,9 +162,15 @@ class OpLevelCostEstimator { } } + // For convolution and its grad ops. static ConvolutionDimensions ConvolutionDimensionsFromInputs( const TensorShapeProto& original_image_shape, - const TensorShapeProto& original_filter_shape, const OpInfo& op_features, + const TensorShapeProto& original_filter_shape, const OpInfo& op_info, + bool* found_unknown_shapes); + + // For Pooling, FusedBatchNorm, and their grad ops. + static ConvolutionDimensions OpDimensionsFromInputs( + const TensorShapeProto& original_image_shape, const OpInfo& op_info, bool* found_unknown_shapes); protected: diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc index 99bf28f21b..56915ed821 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator_test.cc @@ -14,6 +14,8 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/costs/op_level_cost_estimator.h" +#include "tensorflow/core/framework/attr_value.pb.h" +#include "tensorflow/core/framework/attr_value_util.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/tensor_shape.pb.h" @@ -169,6 +171,130 @@ OpContext DescribeBiasAdd(int size1, int size2) { return op_context; } +int GetOutputSize(const int x, const int k, const int s, + const string& padding) { + if (padding == "SAME") { + return (x + s - 1) / s; + } else { + return (x - k + s) / s; + } +} + +std::vector GetPoolingOutputSize(const std::vector& input, + const std::vector& ksize, + const std::vector& strides, + const string& data_format, + const string& padding) { + // h, w, and c indices: default with NHWC. + int h_index = 1; + int w_index = 2; + int c_index = 3; + if (data_format == "NCHW") { + h_index = 2; + w_index = 3; + c_index = 1; + } + // Extract parameters. + int n = input[0]; + int h = input[h_index]; + int w = input[w_index]; + int c = input[c_index]; + int sx = strides[h_index]; + int sy = strides[w_index]; + int kx = ksize[h_index]; + int ky = ksize[w_index]; + + // Output activation size: default with VALID padding. + int ho = GetOutputSize(h, kx, sx, padding); + int wo = GetOutputSize(w, ky, sy, padding); + + std::vector output; + if (data_format == "NHWC") { + output = {n, ho, wo, c}; + } else { + output = {n, c, ho, wo}; + } + return output; +} + +OpContext DescribePoolingOp(const string& op_name, const std::vector& x, + const std::vector& ksize, + const std::vector& strides, + const string& data_format, const string& padding) { + OpContext op_context; + auto& op_info = op_context.op_info; + SetCpuDevice(&op_info); + op_info.set_op(op_name); + + const std::vector y = + GetPoolingOutputSize(x, ksize, strides, data_format, padding); + if (op_name == "AvgPool" || op_name == "MaxPool") { + // input: x, output: y. + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_inputs()); + DescribeTensor4D(y[0], y[1], y[2], y[3], op_info.add_outputs()); + } else if (op_name == "AvgPoolGrad") { + // input: x, y_grad, output: x_grad. + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_inputs()); + DescribeTensor4D(y[0], y[1], y[2], y[3], op_info.add_inputs()); + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_outputs()); + } else if (op_name == "MaxPoolGrad") { + // input: x, y, y_grad, output: x_grad. + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_inputs()); + DescribeTensor4D(y[0], y[1], y[2], y[3], op_info.add_inputs()); + DescribeTensor4D(y[0], y[1], y[2], y[3], op_info.add_inputs()); + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_outputs()); + } + auto* attr = op_info.mutable_attr(); + SetAttrValue(data_format, &(*attr)["data_format"]); + SetAttrValue(padding, &(*attr)["padding"]); + SetAttrValue(strides, &(*attr)["strides"]); + SetAttrValue(ksize, &(*attr)["ksize"]); + return op_context; +} + +OpContext DescribeFusedBatchNorm(const bool is_training, const bool is_grad, + const std::vector& x, + const string& data_format) { + // First, get MaxPool op info with unit stride and unit window. + OpContext op_context = DescribePoolingOp("MaxPool", x, {1, 1, 1, 1}, + {1, 1, 1, 1}, data_format, "SAME"); + auto& op_info = op_context.op_info; + // Override op name. + if (is_grad) { + op_info.set_op("FusedBatchNormGrad"); + } else { + op_info.set_op("FusedBatchNorm"); + } + + // Add additional input output tensors. + if (is_grad) { + DescribeTensor4D(x[0], x[1], x[2], x[3], op_info.add_inputs()); + } + int num_1d_inputs = is_grad ? 3 : 4; + for (int i = 0; i < num_1d_inputs; i++) { + auto* tensor = op_info.add_inputs(); + auto* shape = tensor->mutable_shape(); + shape->add_dim()->set_size(x[3]); + tensor->set_dtype(DT_FLOAT); + } + for (int i = 0; i < 4; i++) { + auto* tensor = op_info.add_outputs(); + auto* shape = tensor->mutable_shape(); + shape->add_dim()->set_size(x[3]); + tensor->set_dtype(DT_FLOAT); + } + + // Delete unnecessary attr. + auto* attr = op_context.op_info.mutable_attr(); + attr->erase("ksize"); + attr->erase("strides"); + attr->erase("padding"); + + // Additional attrs for FusedBatchNorm. + SetAttrValue(is_training, &(*attr)["is_training"]); + + return op_context; +} } // namespace class OpLevelCostEstimatorTest : public ::testing::Test { @@ -192,6 +318,50 @@ class OpLevelCostEstimatorTest : public ::testing::Test { estimator_.compute_memory_overlap_ = value; } + void ValidateOpDimensionsFromImputs(const int n, const int h, const int w, + const int c, const int kx, const int ky, + const int sx, const int sy, + const string& data_format, + const string& padding) { + OpContext op_context; + int ho; + int wo; + if (data_format == "NHWC") { + op_context = DescribePoolingOp("MaxPool", {n, h, w, c}, {1, kx, ky, 1}, + {1, sx, sy, 1}, "NHWC", padding); + ho = op_context.op_info.outputs(0).shape().dim(1).size(); + wo = op_context.op_info.outputs(0).shape().dim(2).size(); + } else { + op_context = DescribePoolingOp("MaxPool", {n, c, h, w}, {1, 1, kx, ky}, + {1, 1, sx, sy}, "NCHW", padding); + ho = op_context.op_info.outputs(0).shape().dim(2).size(); + wo = op_context.op_info.outputs(0).shape().dim(3).size(); + } + + bool found_unknown_shapes; + auto dims = OpLevelCostEstimator::OpDimensionsFromInputs( + op_context.op_info.inputs(0).shape(), op_context.op_info, + &found_unknown_shapes); + Padding padding_enum; + if (padding == "VALID") { + padding_enum = Padding::VALID; + } else { + padding_enum = Padding::SAME; + } + EXPECT_EQ(n, dims.batch); + EXPECT_EQ(h, dims.ix); + EXPECT_EQ(w, dims.iy); + EXPECT_EQ(c, dims.iz); + EXPECT_EQ(kx, dims.kx); + EXPECT_EQ(ky, dims.ky); + EXPECT_EQ(sx, dims.sx); + EXPECT_EQ(sy, dims.sy); + EXPECT_EQ(ho, dims.ox); + EXPECT_EQ(wo, dims.oy); + EXPECT_EQ(c, dims.oz); + EXPECT_EQ(padding_enum, dims.padding); + } + OpLevelCostEstimator estimator_; }; @@ -443,5 +613,226 @@ TEST_F(OpLevelCostEstimatorTest, GetTensorShapeProtoFromTensorProto) { } } +TEST_F(OpLevelCostEstimatorTest, OpDimensionsFromInputs) { + std::vector paddings = {"VALID", "SAME"}; + std::vector formats = {"NHWC", "NCHW"}; + for (const auto& p : paddings) { + for (const auto& f : formats) { + // n, h, w, c, kx, ky, sx, sy, data_format, padding. + ValidateOpDimensionsFromImputs(10, 20, 20, 100, 3, 3, 2, 2, f, p); + ValidateOpDimensionsFromImputs(10, 20, 20, 100, 1, 1, 3, 3, f, p); + ValidateOpDimensionsFromImputs(10, 200, 200, 100, 5, 5, 3, 3, f, p); + ValidateOpDimensionsFromImputs(10, 14, 14, 3840, 3, 3, 2, 2, f, p); + } + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictMaxPool) { + auto predict_max_pool = [this](const int n, const int in, const int c, + const int k, const int s, + const string& padding) -> Costs { + OpContext op_context = DescribePoolingOp( + "MaxPool", {n, in, in, c}, {1, k, k, 1}, {1, s, s, 1}, "NHWC", padding); + return estimator_.PredictCosts(op_context); + }; + + { + // Typical 3xz3 window with 2x2 stride. + auto costs = predict_max_pool(10, 20, 384, 3, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1075200), costs.execution_time); + EXPECT_EQ(Costs::Duration(307200), costs.compute_time); + EXPECT_EQ(Costs::Duration(768000), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 1x1 window with 2x2 stride: used for shortcut in resnet-50. + auto costs = predict_max_pool(10, 20, 384, 1, 2, "SAME"); + EXPECT_EQ(Costs::Duration(499200), costs.execution_time); + EXPECT_EQ(Costs::Duration(38400), costs.compute_time); + EXPECT_EQ(Costs::Duration(460800), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 2x2 window with 3x3 stride. + auto costs = predict_max_pool(10, 20, 384, 2, 3, "VALID"); + EXPECT_EQ(Costs::Duration(561792), costs.execution_time); + EXPECT_EQ(Costs::Duration(56448), costs.compute_time); + EXPECT_EQ(Costs::Duration(505344), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictMaxPoolGrad) { + auto predict_max_pool_grad = [this](const int n, const int in, const int c, + const int k, const int s, + const string& padding) -> Costs { + OpContext op_context = + DescribePoolingOp("MaxPoolGrad", {n, in, in, c}, {1, k, k, 1}, + {1, s, s, 1}, "NHWC", padding); + return estimator_.PredictCosts(op_context); + }; + + { + // Typical 3xz3 window with 2x2 stride. + auto costs = predict_max_pool_grad(10, 20, 384, 3, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1996800), costs.execution_time); + EXPECT_EQ(Costs::Duration(614400), costs.compute_time); + EXPECT_EQ(Costs::Duration(1382400), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 1x1 window with 2x2 stride: used for shortcut in resnet-50. + auto costs = predict_max_pool_grad(10, 20, 384, 1, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1536000), costs.execution_time); + EXPECT_EQ(Costs::Duration(153600), costs.compute_time); + EXPECT_EQ(Costs::Duration(1382400), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 2x2 window with 3x3 stride. + auto costs = predict_max_pool_grad(10, 20, 384, 2, 3, "VALID"); + EXPECT_EQ(Costs::Duration(1514112), costs.execution_time); + EXPECT_EQ(Costs::Duration(210048), costs.compute_time); + EXPECT_EQ(Costs::Duration(1304064), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictAvgPool) { + auto predict_avg_pool = [this](const int n, const int in, const int c, + const int k, const int s, + const string& padding) -> Costs { + OpContext op_context = DescribePoolingOp( + "AvgPool", {n, in, in, c}, {1, k, k, 1}, {1, s, s, 1}, "NHWC", padding); + return estimator_.PredictCosts(op_context); + }; + + { + // Typical 3xz3 window with 2x2 stride. + auto costs = predict_avg_pool(10, 20, 384, 3, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1113600), costs.execution_time); + EXPECT_EQ(Costs::Duration(345600), costs.compute_time); + EXPECT_EQ(Costs::Duration(768000), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 1x1 window with 2x2 stride: used for shortcut in resnet-50. + auto costs = predict_avg_pool(10, 20, 384, 1, 2, "SAME"); + EXPECT_EQ(Costs::Duration(499200), costs.execution_time); + EXPECT_EQ(Costs::Duration(38400), costs.compute_time); + EXPECT_EQ(Costs::Duration(460800), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 2x2 window with 3x3 stride. + auto costs = predict_avg_pool(10, 20, 384, 2, 3, "VALID"); + EXPECT_EQ(Costs::Duration(580608), costs.execution_time); + EXPECT_EQ(Costs::Duration(75264), costs.compute_time); + EXPECT_EQ(Costs::Duration(505344), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictAvgPoolGrad) { + auto predict_avg_pool_grad = [this](const int n, const int in, const int c, + const int k, const int s, + const string& padding) -> Costs { + OpContext op_context = + DescribePoolingOp("AvgPoolGrad", {n, in, in, c}, {1, k, k, 1}, + {1, s, s, 1}, "NHWC", padding); + return estimator_.PredictCosts(op_context); + }; + + { + // Typical 3xz3 window with 2x2 stride. + auto costs = predict_avg_pool_grad(10, 20, 384, 3, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1920000), costs.execution_time); + EXPECT_EQ(Costs::Duration(537600), costs.compute_time); + EXPECT_EQ(Costs::Duration(1382400), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 1x1 window with 2x2 stride: used for shortcut in resnet-50. + auto costs = predict_avg_pool_grad(10, 20, 384, 1, 2, "SAME"); + EXPECT_EQ(Costs::Duration(1574400), costs.execution_time); + EXPECT_EQ(Costs::Duration(192000), costs.compute_time); + EXPECT_EQ(Costs::Duration(1382400), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + { + // 2x2 window with 3x3 stride. + auto costs = predict_avg_pool_grad(10, 20, 384, 2, 3, "VALID"); + EXPECT_EQ(Costs::Duration(1476480), costs.execution_time); + EXPECT_EQ(Costs::Duration(172416), costs.compute_time); + EXPECT_EQ(Costs::Duration(1304064), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictFusedBatchNorm) { + auto predict_fused_bn = [this](const int n, const int in, const int c, + const bool is_training) -> Costs { + OpContext op_context = DescribeFusedBatchNorm( + is_training, /*is_grad=*/false, {n, in, in, c}, "NHWC"); + return estimator_.PredictCosts(op_context); + }; + + { + auto costs = predict_fused_bn(10, 20, 96, /*is_training=*/true); + EXPECT_EQ(Costs::Duration(614737), costs.execution_time); + EXPECT_EQ(Costs::Duration(153706), costs.compute_time); + EXPECT_EQ(Costs::Duration(461031), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + + { + auto costs = predict_fused_bn(10, 20, 32, /*is_training=*/true); + EXPECT_EQ(Costs::Duration(204913), costs.execution_time); + EXPECT_EQ(Costs::Duration(51236), costs.compute_time); + EXPECT_EQ(Costs::Duration(153677), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + + { + auto costs = predict_fused_bn(10, 20, 96, /*is_training=*/false); + EXPECT_EQ(Costs::Duration(384154), costs.execution_time); + EXPECT_EQ(Costs::Duration(76800), costs.compute_time); + EXPECT_EQ(Costs::Duration(307354), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + + { + auto costs = predict_fused_bn(10, 20, 32, /*is_training=*/false); + EXPECT_EQ(Costs::Duration(128052), costs.execution_time); + EXPECT_EQ(Costs::Duration(25600), costs.compute_time); + EXPECT_EQ(Costs::Duration(102452), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} + +TEST_F(OpLevelCostEstimatorTest, PredictFusedBatchNormGrad) { + auto predict_fused_bn_grad = [this](const int n, const int in, + const int c) -> Costs { + OpContext op_context = DescribeFusedBatchNorm( + /*is_training=*/false, /*is_grad=*/true, {n, in, in, c}, "NHWC"); + return estimator_.PredictCosts(op_context); + }; + + { + auto costs = predict_fused_bn_grad(10, 20, 96); + EXPECT_EQ(Costs::Duration(1037050), costs.execution_time); + EXPECT_EQ(Costs::Duration(422496), costs.compute_time); + EXPECT_EQ(Costs::Duration(614554), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } + + { + auto costs = predict_fused_bn_grad(128, 7, 384); + EXPECT_EQ(Costs::Duration(6503809), costs.execution_time); + EXPECT_EQ(Costs::Duration(2649677), costs.compute_time); + EXPECT_EQ(Costs::Duration(3854132), costs.memory_time); + EXPECT_FALSE(costs.inaccurate); + } +} } // end namespace grappler } // end namespace tensorflow -- GitLab From a477242f91010480ca72b052a6adbb50f00ea43b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 13:13:20 -0700 Subject: [PATCH 365/906] Add comment that explicitly states that InitTableIterator is Thread-unsafe. PiperOrigin-RevId: 190821427 --- tensorflow/core/kernels/initializable_lookup_table.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/kernels/initializable_lookup_table.h b/tensorflow/core/kernels/initializable_lookup_table.h index b16c76dc7f..edb779540f 100644 --- a/tensorflow/core/kernels/initializable_lookup_table.h +++ b/tensorflow/core/kernels/initializable_lookup_table.h @@ -92,6 +92,8 @@ class InitializableLookupTable : public LookupInterface { // // Then the iterator is exhausted, valid returns false and status returns // Status::OutOfRange. + // + // This class is Thread-unsafe. class InitTableIterator { public: InitTableIterator() {} -- GitLab From 01583b714c4144dbf11e1f2ae5189f051d130d13 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 13:21:05 -0700 Subject: [PATCH 366/906] [XLA] Redesign: add the rest of client-service interfaces. The basic idea is, on the client side, for each public method that has a Computation parameter, add an overload with XlaCompuation. If such method needs to call the service side, add corresponding service interfaces. Also make XlaCompuation::GetProgramShape return StatusOr, to be consistent with the Computation class. PiperOrigin-RevId: 190822601 --- tensorflow/compiler/xla/client/client.cc | 19 ++++++++ tensorflow/compiler/xla/client/client.h | 44 +++++++++++++++++++ .../xla/client/xla_client/xla_builder.cc | 10 +++-- .../xla/client/xla_client/xla_computation.cc | 5 ++- .../xla/client/xla_client/xla_computation.h | 3 +- tensorflow/compiler/xla/service/service.cc | 10 +++++ tensorflow/compiler/xla/service/service.h | 16 +++++++ tensorflow/compiler/xla/service_interface.h | 8 ++++ tensorflow/compiler/xla/xla.proto | 9 ++++ 9 files changed, 118 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/client/client.cc b/tensorflow/compiler/xla/client/client.cc index 5ce3c45528..a857c4ff0b 100644 --- a/tensorflow/compiler/xla/client/client.cc +++ b/tensorflow/compiler/xla/client/client.cc @@ -317,6 +317,12 @@ StatusOr>> Client::ExecuteParallel( return std::move(outputs); } +StatusOr>> Client::ExecuteParallel( + tensorflow::gtl::ArraySlice computations) { + return Unimplemented( + "ExecuteParallel is not yet implemented for XlaComputation."); +} + StatusOr> Client::GetDeviceHandles( int64 device_count) { if (device_count < 1) { @@ -393,6 +399,13 @@ StatusOr Client::GetComputationStats( return response.stats(); } +StatusOr Client::GetComputationStats( + const XlaComputation& computation, + const DebugOptions& debug_options) const { + return Unimplemented( + "GetComputationStats is not yet implemented for XlaComputation"); +} + StatusOr> Client::GetComputationShape( const Computation& computation) { GetComputationShapeRequest request; @@ -410,6 +423,12 @@ StatusOr> Client::GetComputationShape( return WrapUnique(response.release_program_shape()); } +StatusOr> Client::GetComputationShape( + const XlaComputation& computation) { + TF_ASSIGN_OR_RETURN(const auto& result, computation.GetProgramShape()); + return MakeUnique(result); +} + StatusOr Client::GetShape(const GlobalData& data) { GetShapeRequest request; *request.mutable_data() = data.handle(); diff --git a/tensorflow/compiler/xla/client/client.h b/tensorflow/compiler/xla/client/client.h index ec87646ebf..226b788d54 100644 --- a/tensorflow/compiler/xla/client/client.h +++ b/tensorflow/compiler/xla/client/client.h @@ -99,6 +99,36 @@ class Client { StatusOr>> ExecuteParallel( tensorflow::gtl::ArraySlice computations); + // A struct to represent a computation instance to be executed. + // * If execution_options.device_handles is not empty, the computation is + // executed on the devices associated with the handles by partitioning the + // computation based on the attached sharding attributes. Otherwise, a + // device is chosen by the service. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + struct XlaComputationInstance { + const XlaComputation& computation; + std::vector arguments; + ExecutionOptions execution_options; + ExecutionProfile* execution_profile; + + XlaComputationInstance(const XlaComputation& computation, + std::vector arguments, + ExecutionOptions execution_options, + ExecutionProfile* execution_profile) + : computation(computation), + arguments(std::move(arguments)), + execution_options(execution_options), + execution_profile(execution_profile) {} + }; + + // Executes a list XlaComputationInstances and returns global data produced + // from each computation. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr>> ExecuteParallel( + tensorflow::gtl::ArraySlice computations); + // Requests device_count device handles available on the target. The returned // device handles are used to specify the devices to execute the computations // (see ExecuteParallel) or to transfer data (see TransferToServer or @@ -175,6 +205,13 @@ class Client { StatusOr GetComputationStats( const Computation& computation, const DebugOptions& debug_options) const; + // Retrieves the statistics of the given computation. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr GetComputationStats( + const XlaComputation& computation, + const DebugOptions& debug_options) const; + // Returns the Shape of the given array specified by 'data'. The shape // includes the Layout of the array as it is stored on the service. StatusOr GetShape(const GlobalData& data); @@ -184,6 +221,13 @@ class Client { StatusOr> GetComputationShape( const Computation& computation); + // As above, but returns the shape of the provided computation (parameter + // types/names and return type). + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + StatusOr> GetComputationShape( + const XlaComputation& computation); + // Creates a channel handle that can be used to transfer data between // two computations via a pair of Send and Recv instructions. StatusOr CreateChannelHandle(); diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc index 1b94f9a4eb..e51a8b14c0 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.cc @@ -369,10 +369,12 @@ XlaOp XlaBuilder::Call(const XlaComputation& computation, } c_transform(operand_shapes, std::back_inserter(operand_shape_ptrs), [](const Shape& shape) { return &shape; }); - TF_ASSIGN_OR_RETURN(*instr.mutable_shape(), - ShapeInference::InferCallShape( - operand_shape_ptrs, - /*to_apply=*/computation.GetProgramShape())); + TF_ASSIGN_OR_RETURN(const ProgramShape& called_program_shape, + computation.GetProgramShape()); + TF_ASSIGN_OR_RETURN( + *instr.mutable_shape(), + ShapeInference::InferCallShape(operand_shape_ptrs, + /*to_apply=*/called_program_shape)); // Add called computation. instr.add_called_computation_ids( diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.cc b/tensorflow/compiler/xla/client/xla_client/xla_computation.cc index 3681792eee..a6752c6010 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_computation.cc +++ b/tensorflow/compiler/xla/client/xla_client/xla_computation.cc @@ -17,9 +17,12 @@ limitations under the License. #include +#include "tensorflow/compiler/xla/status_macros.h" + namespace xla { -const ProgramShape& XlaComputation::GetProgramShape() const { +StatusOr XlaComputation::GetProgramShape() const { + TF_RET_CHECK(proto_.has_program_shape()); return proto_.program_shape(); } diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.h b/tensorflow/compiler/xla/client/xla_client/xla_computation.h index 78e1e3c32c..2a3c695266 100644 --- a/tensorflow/compiler/xla/client/xla_client/xla_computation.h +++ b/tensorflow/compiler/xla/client/xla_client/xla_computation.h @@ -40,7 +40,8 @@ class XlaComputation { // Returns the "program shape" (parameter and return shapes) for this // computation. - const ProgramShape& GetProgramShape() const; + StatusOr GetProgramShape() const; + const HloModuleProto& proto() const { return proto_; } private: diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 1d379f0d03..af05e3f516 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -837,6 +837,11 @@ tensorflow::Status Service::ExecuteParallel(const ExecuteParallelRequest* arg, return tensorflow::Status::OK(); } +tensorflow::Status Service::ExecuteGraphParallel( + const ExecuteGraphParallelRequest* arg, ExecuteParallelResponse* result) { + return Unimplemented("execute-graph-parallel is not yet implemented"); +} + tensorflow::Status Service::GetDeviceHandles(const GetDeviceHandlesRequest* arg, GetDeviceHandlesResponse* result) { const int64 available_device_count = execute_backend_->device_count(); @@ -1445,6 +1450,11 @@ tensorflow::Status Service::GetComputationStats( return tensorflow::Status::OK(); } +tensorflow::Status Service::GetComputationGraphStats( + const ComputationGraphStatsRequest* arg, ComputationStatsResponse* result) { + return Unimplemented("get-computation-graph-stats is not yet implemented"); +} + template tensorflow::Status Service::AddInstruction( const RequestT* arg, ResponseT* result, diff --git a/tensorflow/compiler/xla/service/service.h b/tensorflow/compiler/xla/service/service.h index 773f0a642d..ebe4a2e043 100644 --- a/tensorflow/compiler/xla/service/service.h +++ b/tensorflow/compiler/xla/service/service.h @@ -126,6 +126,15 @@ class Service : public ServiceInterface { tensorflow::Status ExecuteParallel(const ExecuteParallelRequest* arg, ExecuteParallelResponse* result) override; + // Executes one or more computations in parallel with the provided global data + // passed as immutable arguments. Returns global data output for each + // computation. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + tensorflow::Status ExecuteGraphParallel( + const ExecuteGraphParallelRequest* arg, + ExecuteParallelResponse* result) override; + // Requests one or more device handles from the target. // // When N device handles are requested and the number of replicas is R, at @@ -224,6 +233,13 @@ class Service : public ServiceInterface { const ComputationStatsRequest* arg, ComputationStatsResponse* result) override; + // Retrieves the statistics of a computation. + // + // TODO(b/74197823): This is a part of a NOT YET ready refactor. + tensorflow::Status GetComputationGraphStats( + const ComputationGraphStatsRequest* arg, + ComputationStatsResponse* result) override; + // Snapshots the current state of a computation handle into a serializable // protocol buffer form, so it can be loaded via // LoadComputationSnapshot. diff --git a/tensorflow/compiler/xla/service_interface.h b/tensorflow/compiler/xla/service_interface.h index d8235113dd..32aae64973 100644 --- a/tensorflow/compiler/xla/service_interface.h +++ b/tensorflow/compiler/xla/service_interface.h @@ -60,6 +60,10 @@ class ServiceInterface { virtual tensorflow::Status ExecuteParallel( const ExecuteParallelRequest* arg, ExecuteParallelResponse* result) = 0; + virtual tensorflow::Status ExecuteGraphParallel( + const ExecuteGraphParallelRequest* arg, + ExecuteParallelResponse* result) = 0; + virtual tensorflow::Status ExecuteAsync(const ExecuteAsyncRequest* arg, ExecuteAsyncResponse* result) = 0; @@ -72,6 +76,10 @@ class ServiceInterface { virtual tensorflow::Status GetComputationStats( const ComputationStatsRequest* arg, ComputationStatsResponse* result) = 0; + virtual tensorflow::Status GetComputationGraphStats( + const ComputationGraphStatsRequest* arg, + ComputationStatsResponse* result) = 0; + virtual tensorflow::Status GetComputationShape( const GetComputationShapeRequest* arg, GetComputationShapeResponse* result) = 0; diff --git a/tensorflow/compiler/xla/xla.proto b/tensorflow/compiler/xla/xla.proto index edf1b07af8..5cb18113e5 100644 --- a/tensorflow/compiler/xla/xla.proto +++ b/tensorflow/compiler/xla/xla.proto @@ -299,6 +299,11 @@ message ComputationStatsRequest { DebugOptions debug_options = 2; } +message ComputationGraphStatsRequest { + HloModuleProto computation = 1; + DebugOptions debug_options = 2; +} + message ComputationStatsResponse { ComputationStats stats = 1; } @@ -355,6 +360,10 @@ message ExecuteParallelRequest { repeated ExecuteRequest requests = 1; } +message ExecuteGraphParallelRequest { + repeated ExecuteGraphRequest requests = 1; +} + message ExecuteResponse { GlobalDataHandle output = 1; ExecutionProfile profile = 2; -- GitLab From 70666858800a55585ae2775f97a1731db305388a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 13:27:07 -0700 Subject: [PATCH 367/906] Make sure tensor size match before inspecting their content. PiperOrigin-RevId: 190823557 --- .../contrib/lite/testing/tflite_driver.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/lite/testing/tflite_driver.cc b/tensorflow/contrib/lite/testing/tflite_driver.cc index 613223f3d4..c399f4f2b7 100644 --- a/tensorflow/contrib/lite/testing/tflite_driver.cc +++ b/tensorflow/contrib/lite/testing/tflite_driver.cc @@ -56,12 +56,16 @@ void SetTensorData(const std::vector& values, TfLitePtrUnion* data) { class TfLiteDriver::Expectation { public: - Expectation() { data_.raw = nullptr; } + Expectation() { + data_.raw = nullptr; + num_elements_ = 0; + } ~Expectation() { delete[] data_.raw; } template void SetData(const string& csv_values) { const auto& values = testing::Split(csv_values, ","); - data_.raw = new char[values.size() * sizeof(T)]; + num_elements_ = values.size(); + data_.raw = new char[num_elements_ * sizeof(T)]; SetTensorData(values, &data_); } @@ -88,7 +92,13 @@ class TfLiteDriver::Expectation { constexpr double kRelativeThreshold = 1e-2f; constexpr double kAbsoluteThreshold = 1e-4f; - int tensor_size = tensor.bytes / sizeof(T); + size_t tensor_size = tensor.bytes / sizeof(T); + + if (tensor_size != num_elements_) { + std::cerr << "Expected a tensor with " << num_elements_ + << " elements, got " << tensor_size << std::endl; + return false; + } bool good_output = true; for (int i = 0; i < tensor_size; ++i) { @@ -115,6 +125,7 @@ class TfLiteDriver::Expectation { } TfLitePtrUnion data_; + size_t num_elements_; }; TfLiteDriver::TfLiteDriver(bool use_nnapi) : use_nnapi_(use_nnapi) {} -- GitLab From d355f4e2644b68ea643f573c564936ec23b93787 Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Wed, 28 Mar 2018 14:04:01 -0700 Subject: [PATCH 368/906] [tf.data] Autotune prefetch buffer sizes In order to make it easier for tf.data users to achieve high performance with their input pipelines, this change adds the ability for the prefetch op to automatically tune its buffer size. To use the auto-tuning configuration of the `prefetch` transformation, simply skip passing in a buffer size. Example: ```python dataset = # ... dataset = dataset.prefetch() # Look ma, no buffer value req'd! ``` PiperOrigin-RevId: 190829736 --- tensorflow/contrib/data/__init__.py | 3 + tensorflow/core/kernels/data/BUILD | 21 +++++ .../core/kernels/data/prefetch_autotuner.cc | 46 +++++++++++ .../core/kernels/data/prefetch_autotuner.h | 71 ++++++++++++++++ .../kernels/data/prefetch_autotuner_test.cc | 82 +++++++++++++++++++ .../core/kernels/data/prefetch_dataset_op.cc | 13 ++- tensorflow/python/data/ops/dataset_ops.py | 2 + 7 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 tensorflow/core/kernels/data/prefetch_autotuner.cc create mode 100644 tensorflow/core/kernels/data/prefetch_autotuner.h create mode 100644 tensorflow/core/kernels/data/prefetch_autotuner_test.cc diff --git a/tensorflow/contrib/data/__init__.py b/tensorflow/contrib/data/__init__.py index 766721d8d2..7c3a9f82ff 100644 --- a/tensorflow/contrib/data/__init__.py +++ b/tensorflow/contrib/data/__init__.py @@ -82,3 +82,6 @@ from tensorflow.python.ops.parsing_ops import parse_single_example_v2 as parse_s from tensorflow.python.util.all_util import remove_undocumented remove_undocumented(__name__) + +# A constant that can be used to enable auto-tuning. +AUTOTUNE = -1 diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index 01754ec21a..a8784e3656 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -10,6 +10,7 @@ licenses(["notice"]) # Apache 2.0 load( "//tensorflow:tensorflow.bzl", "tf_kernel_library", + "tf_cc_test", ) filegroup( @@ -295,11 +296,31 @@ tf_kernel_library( ], ) +cc_library( + name = "prefetch_autotuner", + srcs = ["prefetch_autotuner.cc"], + hdrs = ["prefetch_autotuner.h"], + deps = [ + "//tensorflow/core:lib", + ], +) + +tf_cc_test( + name = "prefetch_autotuner_test", + srcs = ["prefetch_autotuner_test.cc"], + deps = [ + ":prefetch_autotuner", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + tf_kernel_library( name = "prefetch_dataset_op", srcs = ["prefetch_dataset_op.cc"], deps = [ ":dataset", + ":prefetch_autotuner", "//tensorflow/core:core_cpu_internal", "//tensorflow/core:dataset_ops_op_lib", "//tensorflow/core:framework", diff --git a/tensorflow/core/kernels/data/prefetch_autotuner.cc b/tensorflow/core/kernels/data/prefetch_autotuner.cc new file mode 100644 index 0000000000..b3272f6bcd --- /dev/null +++ b/tensorflow/core/kernels/data/prefetch_autotuner.cc @@ -0,0 +1,46 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/kernels/data/prefetch_autotuner.h" + +namespace tensorflow { + +PrefetchAutotuner::PrefetchAutotuner(int64 initial_buffer_size) + : buffer_limit_(initial_buffer_size) { + if (initial_buffer_size == kAutoTune) { + mode_ = Mode::kUpswing; + buffer_limit_ = 1; + } +} + +void PrefetchAutotuner::RecordConsumption(size_t current_buffer_size) { + switch (mode_) { + case Mode::kDisabled: + return; + case Mode::kUpswing: + if (current_buffer_size == buffer_limit_) { + mode_ = Mode::kDownswing; + } + return; + case Mode::kDownswing: + if (current_buffer_size == 0) { + buffer_limit_ *= 2; // Increase the buffer size. + mode_ = Mode::kUpswing; + } + return; + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/kernels/data/prefetch_autotuner.h b/tensorflow/core/kernels/data/prefetch_autotuner.h new file mode 100644 index 0000000000..fa8a184072 --- /dev/null +++ b/tensorflow/core/kernels/data/prefetch_autotuner.h @@ -0,0 +1,71 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_KERNELS_DATA_PREFETCH_AUTOTUNER_H_ +#define TENSORFLOW_CORE_KERNELS_DATA_PREFETCH_AUTOTUNER_H_ + +#include "tensorflow/core/platform/types.h" + +namespace tensorflow { + +// PrefetchAutotuner dynamically adjusts the buffer size of a prefetch iterator. +// +// PrefetchAutotuner attempts to find the minimum buffer size such that there is +// always at least 1 element in the prefetch queue every time the downstream +// iterator calls GetNext(). +// +// One common failure mode of input pipelines is being throughput bound. No +// amount of prefetching can address that performance mode. In order to guard +// against this condition, PrefetchAutotuner will only increase the buffer_limit +// if the prefetching thread is able to successfully fill the buffer at its +// current size. +// +// Note: in the current implementation, we never decrease the buffer_limit(). +// This should change in the future! +// +// PrefetchAutotuner is NOT thread safe. +class PrefetchAutotuner { + public: + static const int64 kAutoTune = -1; + + explicit PrefetchAutotuner(int64 initial_buffer_size); + + int64 buffer_limit() const { return buffer_limit_; } + + void RecordConsumption(size_t current_buffer_size); + void RecordEmpty() { RecordConsumption(0); } + + private: + // PrefetchAutotuner operates as a state machine. + enum class Mode { + // Disables the autotuning. + kDisabled, + + // We have increased the size of the buffer, and will transition to + // kDownswing if we successfully fill the buffer. + kUpswing, + + // We have successfully filled a buffer of this size. If we ever block the + // downstream iterator, we should increase the buffer size. + kDownswing, + }; + + int64 buffer_limit_; + Mode mode_ = Mode::kDisabled; +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_KERNELS_DATA_PREFETCH_AUTOTUNER_H_ diff --git a/tensorflow/core/kernels/data/prefetch_autotuner_test.cc b/tensorflow/core/kernels/data/prefetch_autotuner_test.cc new file mode 100644 index 0000000000..2f573dfb35 --- /dev/null +++ b/tensorflow/core/kernels/data/prefetch_autotuner_test.cc @@ -0,0 +1,82 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/kernels/data/prefetch_autotuner.h" + +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace { + +TEST(PrefetchAutotuner, Disabled) { + PrefetchAutotuner t(2); + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(0); + t.RecordConsumption(2); + t.RecordConsumption(0); + t.RecordConsumption(2); + EXPECT_EQ(2, t.buffer_limit()); +} + +TEST(PrefetchAutotuner, Enabled) { + PrefetchAutotuner t(PrefetchAutotuner::kAutoTune); + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(1); + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(2); + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(1); + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(4, t.buffer_limit()); + t.RecordConsumption(4); + EXPECT_EQ(4, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(8, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to stay the same! + EXPECT_EQ(8, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to stay the same! + EXPECT_EQ(8, t.buffer_limit()); +} + +TEST(PrefetchAutotuner, EnabledSteady) { + PrefetchAutotuner t(PrefetchAutotuner::kAutoTune); + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(1); + EXPECT_EQ(1, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(2); + EXPECT_EQ(2, t.buffer_limit()); + t.RecordConsumption(0); // Expect buffer limit to increase. + EXPECT_EQ(4, t.buffer_limit()); + + // Never reach zero again. + std::vector consumption_values = {2, 3, 1, 4, 1, 2, 3, 1}; + for (int i = 0; i < consumption_values.size(); ++i) { + t.RecordConsumption(consumption_values[i]); + EXPECT_EQ(4, t.buffer_limit()) + << "Failed at index " << i << " with value: " << consumption_values[i]; + } +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/kernels/data/prefetch_dataset_op.cc b/tensorflow/core/kernels/data/prefetch_dataset_op.cc index 1c548a30d2..536de81fd8 100644 --- a/tensorflow/core/kernels/data/prefetch_dataset_op.cc +++ b/tensorflow/core/kernels/data/prefetch_dataset_op.cc @@ -17,6 +17,7 @@ limitations under the License. #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/kernels/data/dataset.h" +#include "tensorflow/core/kernels/data/prefetch_autotuner.h" #include "tensorflow/core/lib/core/error_codes.pb.h" namespace tensorflow { @@ -37,7 +38,8 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { int64 buffer_size; OP_REQUIRES_OK( ctx, ParseScalarArgument(ctx, "buffer_size", &buffer_size)); - OP_REQUIRES(ctx, buffer_size > 0, + OP_REQUIRES(ctx, + buffer_size > 0 || buffer_size == PrefetchAutotuner::kAutoTune, errors::InvalidArgument("buffer_size must be > 0")); *output = new Dataset(ctx, input, buffer_size); @@ -85,7 +87,8 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { public: explicit Iterator(const Params& params) : DatasetIterator(params), - input_impl_(params.dataset->input_->MakeIterator(params.prefix)) {} + input_impl_(params.dataset->input_->MakeIterator(params.prefix)), + auto_tuner_(params.dataset->buffer_size_) {} ~Iterator() override { // Signal the prefetch thread to terminate it. We will then @@ -113,6 +116,7 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { // Wait until the next element in the buffer has been // produced, or we are shutting down. while (!cancelled_ && !prefetch_thread_finished_ && buffer_.empty()) { + auto_tuner_.RecordEmpty(); cond_var_.wait(l); } @@ -129,6 +133,7 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { if (s.ok()) { *out_tensors = std::move(buffer_.front().value); } + auto_tuner_.RecordConsumption(buffer_.size()); buffer_.pop_front(); *end_of_sequence = false; @@ -242,7 +247,8 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { // 1. Wait for a slot in the buffer. { mutex_lock l(mu_); - while (!cancelled_ && buffer_.size() == dataset()->buffer_size_) { + while (!cancelled_ && + buffer_.size() == auto_tuner_.buffer_limit()) { cond_var_.wait(l); } @@ -323,6 +329,7 @@ class PrefetchDatasetOp : public UnaryDatasetOpKernel { mutex parent_mu_ ACQUIRED_BEFORE(mu_); const std::unique_ptr input_impl_ GUARDED_BY(parent_mu_); condition_variable cond_var_; + PrefetchAutotuner auto_tuner_ GUARDED_BY(mu_); std::deque buffer_ GUARDED_BY(mu_); std::unique_ptr prefetch_thread_ GUARDED_BY(mu_); bool cancelled_ GUARDED_BY(mu_) = false; diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index c0a6283be4..8729e085a3 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2043,6 +2043,8 @@ class PrefetchDataset(Dataset): """See `Dataset.prefetch()` for details.""" super(PrefetchDataset, self).__init__() self._input_dataset = input_dataset + if buffer_size is None: + buffer_size = -1 # This is the sentinel for auto-tuning. self._buffer_size = ops.convert_to_tensor( buffer_size, dtype=dtypes.int64, name="buffer_size") -- GitLab From 3c0229c36ad7ade3cf795e3171c3c563e0222ed2 Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Thu, 29 Mar 2018 05:28:16 +0800 Subject: [PATCH 369/906] Fix broken wiki link of Positive-definite_matrix in linalg api guide (#18057) * Fix broken wiki link of Positive-definite_matrix in linalg api guide * Fix minor intent --- .../contrib/linalg/python/ops/linear_operator_block_diag.py | 3 +-- tensorflow/python/ops/linalg/linear_operator.py | 3 +-- tensorflow/python/ops/linalg/linear_operator_composition.py | 3 +-- tensorflow/python/ops/linalg/linear_operator_diag.py | 3 +-- tensorflow/python/ops/linalg/linear_operator_full_matrix.py | 3 +-- tensorflow/python/ops/linalg/linear_operator_identity.py | 6 ++---- .../python/ops/linalg/linear_operator_lower_triangular.py | 3 +-- 7 files changed, 8 insertions(+), 16 deletions(-) diff --git a/tensorflow/contrib/linalg/python/ops/linear_operator_block_diag.py b/tensorflow/contrib/linalg/python/ops/linear_operator_block_diag.py index 80649bd52d..9d3af66c92 100644 --- a/tensorflow/contrib/linalg/python/ops/linear_operator_block_diag.py +++ b/tensorflow/contrib/linalg/python/ops/linear_operator_block_diag.py @@ -138,8 +138,7 @@ class LinearOperatorBlockDiag(linear_operator.LinearOperator): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. This is true by default, and will raise a `ValueError` otherwise. name: A name for this `LinearOperator`. Default is the individual diff --git a/tensorflow/python/ops/linalg/linear_operator.py b/tensorflow/python/ops/linalg/linear_operator.py index c7513d5b40..193c787baa 100644 --- a/tensorflow/python/ops/linalg/linear_operator.py +++ b/tensorflow/python/ops/linalg/linear_operator.py @@ -166,8 +166,7 @@ class LinearOperator(object): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. diff --git a/tensorflow/python/ops/linalg/linear_operator_composition.py b/tensorflow/python/ops/linalg/linear_operator_composition.py index ecd30e4d7e..0292bc51dc 100644 --- a/tensorflow/python/ops/linalg/linear_operator_composition.py +++ b/tensorflow/python/ops/linalg/linear_operator_composition.py @@ -134,8 +134,7 @@ class LinearOperatorComposition(linear_operator.LinearOperator): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. Default is the individual operators names joined with `_o_`. diff --git a/tensorflow/python/ops/linalg/linear_operator_diag.py b/tensorflow/python/ops/linalg/linear_operator_diag.py index e180e83026..5beaea65a5 100644 --- a/tensorflow/python/ops/linalg/linear_operator_diag.py +++ b/tensorflow/python/ops/linalg/linear_operator_diag.py @@ -132,8 +132,7 @@ class LinearOperatorDiag(linear_operator.LinearOperator): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. diff --git a/tensorflow/python/ops/linalg/linear_operator_full_matrix.py b/tensorflow/python/ops/linalg/linear_operator_full_matrix.py index f979fb37d6..5ba3b090ae 100644 --- a/tensorflow/python/ops/linalg/linear_operator_full_matrix.py +++ b/tensorflow/python/ops/linalg/linear_operator_full_matrix.py @@ -125,8 +125,7 @@ class LinearOperatorFullMatrix(linear_operator.LinearOperator): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. diff --git a/tensorflow/python/ops/linalg/linear_operator_identity.py b/tensorflow/python/ops/linalg/linear_operator_identity.py index 50f3d407e8..45929eb4e2 100644 --- a/tensorflow/python/ops/linalg/linear_operator_identity.py +++ b/tensorflow/python/ops/linalg/linear_operator_identity.py @@ -236,8 +236,7 @@ class LinearOperatorIdentity(BaseLinearOperatorIdentity): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. assert_proper_shapes: Python `bool`. If `False`, only perform static checks that initialization and method arguments have proper shape. @@ -576,8 +575,7 @@ class LinearOperatorScaledIdentity(BaseLinearOperatorIdentity): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. assert_proper_shapes: Python `bool`. If `False`, only perform static checks that initialization and method arguments have proper shape. diff --git a/tensorflow/python/ops/linalg/linear_operator_lower_triangular.py b/tensorflow/python/ops/linalg/linear_operator_lower_triangular.py index a5130188b6..c4d386ccb4 100644 --- a/tensorflow/python/ops/linalg/linear_operator_lower_triangular.py +++ b/tensorflow/python/ops/linalg/linear_operator_lower_triangular.py @@ -133,8 +133,7 @@ class LinearOperatorLowerTriangular(linear_operator.LinearOperator): meaning the quadratic form `x^H A x` has positive real part for all nonzero `x`. Note that we do not require the operator to be self-adjoint to be positive-definite. See: - https://en.wikipedia.org/wiki/Positive-definite_matrix\ - #Extension_for_non_symmetric_matrices + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices is_square: Expect that this operator acts like square [batch] matrices. name: A name for this `LinearOperator`. -- GitLab From ef6552b544b3c3bf6808be807b30dd9bd4f19669 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Wed, 28 Mar 2018 14:30:39 -0700 Subject: [PATCH 370/906] [tf.data] Fix reference leak in FunctionBufferingResource. Previously, the FunctionBufferingResource's destructor would never be called, which led to use-after-free (of the underlying Device object) errors in the prefetching function. PiperOrigin-RevId: 190834415 --- tensorflow/contrib/cmake/tf_tests.cmake | 1 + tensorflow/contrib/data/kernels/prefetching_kernels.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/tensorflow/contrib/cmake/tf_tests.cmake b/tensorflow/contrib/cmake/tf_tests.cmake index 237f4fe33a..f793877c8b 100644 --- a/tensorflow/contrib/cmake/tf_tests.cmake +++ b/tensorflow/contrib/cmake/tf_tests.cmake @@ -281,6 +281,7 @@ if (tensorflow_BUILD_PYTHON_TESTS) "${tensorflow_source_dir}/tensorflow/python/data/kernel_tests/iterator_ops_cluster_test.py" "${tensorflow_source_dir}/tensorflow/contrib/data/python/kernel_tests/interleave_dataset_op_test.py" # Deadlocks "${tensorflow_source_dir}/tensorflow/contrib/data/python/kernel_tests/sloppy_transformation_dataset_op_test.py" # b/65430561 + "${tensorflow_source_dir}/tensorflow/contrib/data/python/kernel_tests/prefetching_ops_test.py" # Segfaults on Windows. # tensor_forest tests (also note that we exclude the hybrid tests for now) "${tensorflow_source_dir}/tensorflow/contrib/tensor_forest/python/kernel_tests/count_extremely_random_stats_op_test.py" # Results in wrong order. "${tensorflow_source_dir}/tensorflow/contrib/tensor_forest/python/kernel_tests/sample_inputs_op_test.py" # Results in wrong order. diff --git a/tensorflow/contrib/data/kernels/prefetching_kernels.cc b/tensorflow/contrib/data/kernels/prefetching_kernels.cc index 79d1fc3494..f51570db85 100644 --- a/tensorflow/contrib/data/kernels/prefetching_kernels.cc +++ b/tensorflow/contrib/data/kernels/prefetching_kernels.cc @@ -314,6 +314,7 @@ class FunctionBufferResourceHandleOp : public OpKernel { source_device, target_device, func_args, thread_pool_size_); return Status::OK(); })); + core::ScopedUnref s(buffer); OP_REQUIRES_OK(ctx, buffer->Instantiate()); initialized_ = true; } -- GitLab From e97c9e91e016efd951dc52e82744f607d948bb2a Mon Sep 17 00:00:00 2001 From: Jianwei Xie Date: Wed, 28 Mar 2018 14:36:18 -0700 Subject: [PATCH 371/906] Merge changes from github. PiperOrigin-RevId: 190835392 --- RELEASE.md | 60 +++ configure.py | 2 +- tensorflow/BUILD | 7 + tensorflow/contrib/BUILD | 27 +- .../boosted_trees/kernels/quantile_ops.cc | 2 +- .../boosted_trees/lib/utils/batch_features.cc | 2 +- .../lib/utils/batch_features_test.cc | 2 +- .../boosted_trees/lib/utils/dropout_utils.cc | 2 +- .../boosted_trees/lib/utils/dropout_utils.h | 2 +- .../lib/utils/sparse_column_iterable_test.cc | 2 +- .../boosted_trees/proto/tree_config.proto | 2 +- .../kernel_tests/prediction_ops_test.py | 10 +- .../python/kernel_tests/quantile_ops_test.py | 2 +- .../boosted_trees/python/ops/quantile_ops.py | 2 +- tensorflow/contrib/cmake/tf_tests.cmake | 3 + .../kernel_tests/batch_dataset_op_test.py | 14 + tensorflow/contrib/eager/python/BUILD | 6 +- .../eager/python/examples/spinn/spinn_test.py | 1 - .../python/estimator/replicate_model_fn.py | 2 +- .../factorization/kernels/clustering_ops.cc | 2 +- .../python/ops/factorization_ops.py | 14 +- .../python/ops/factorization_ops_test.py | 12 +- .../factorization/python/ops/gmm_ops.py | 4 +- .../factorization/python/ops/gmm_test.py | 2 +- .../factorization/python/ops/kmeans_test.py | 4 +- .../contrib/factorization/python/ops/wals.py | 2 +- tensorflow/contrib/learn/BUILD | 1 + .../learn/python/learn/estimators/linear.py | 4 +- .../linear_optimizer/python/sdca_estimator.py | 4 +- tensorflow/contrib/lite/README.md | 3 + tensorflow/contrib/lite/builtin_ops.h | 1 + tensorflow/contrib/lite/g3doc/models.md | 2 +- tensorflow/contrib/lite/kernels/BUILD | 13 + .../internal/reference/reference_ops.h | 25 ++ tensorflow/contrib/lite/kernels/maximum.cc | 106 +++++ .../contrib/lite/kernels/maximum_test.cc | 81 ++++ tensorflow/contrib/lite/kernels/register.cc | 2 + tensorflow/contrib/lite/model.cc | 3 + tensorflow/contrib/lite/nnapi_delegate.cc | 1 + tensorflow/contrib/lite/python/lite.py | 22 +- tensorflow/contrib/lite/schema/schema.fbs | 5 + .../contrib/lite/schema/schema_generated.h | 124 +++++- tensorflow/contrib/lite/testing/BUILD | 1 + .../contrib/lite/testing/generate_examples.py | 36 ++ .../testing/generated_examples_zip_test.cc | 1 + .../contrib/lite/toco/tflite/operator.cc | 2 + .../contrib/lite/toco/tflite/operator_test.cc | 2 + tensorflow/contrib/lookup/lookup_ops.py | 2 +- .../contrib/makefile/download_dependencies.sh | 2 +- tensorflow/contrib/makefile/tf_op_files.txt | 1 + .../seq2seq/kernels/beam_search_ops.cc | 2 +- .../seq2seq/python/ops/attention_wrapper.py | 8 +- .../seq2seq/python/ops/beam_search_decoder.py | 6 +- .../slim/python/slim/data/parallel_reader.py | 4 +- .../slim/python/slim/data/prefetch_queue.py | 4 +- .../python/slim/data/tfexample_decoder.py | 2 +- tensorflow/contrib/tensorrt/README.md | 46 ++- .../contrib/tensorrt/convert/convert_graph.cc | 20 +- .../contrib/tensorrt/convert/convert_nodes.cc | 375 ++++++++++-------- .../contrib/tensorrt/segment/segment.cc | 55 ++- tensorflow/contrib/tensorrt/segment/segment.h | 4 +- .../contrib/tensorrt/segment/segment_test.cc | 8 +- .../timeseries/python/timeseries/ar_model.py | 2 +- .../python/timeseries/math_utils.py | 2 +- .../timeseries/state_space_models/varma.py | 4 +- .../base_api/api_def_MatrixSolveLs.pbtxt | 6 +- .../core/common_runtime/mkl_cpu_allocator.cc | 3 - tensorflow/core/framework/common_shape_fns.cc | 4 +- tensorflow/core/framework/common_shape_fns.h | 8 +- tensorflow/core/framework/shape_inference.h | 1 + .../core/kernels/mkl_fused_batch_norm_op.cc | 2 +- .../core/kernels/segment_reduction_ops.h | 7 + tensorflow/core/kernels/snapshot_op.cc | 30 ++ tensorflow/core/kernels/snapshot_op.h | 26 +- tensorflow/core/kernels/snapshot_op_gpu.cu.cc | 9 +- tensorflow/core/kernels/xent_op.cc | 65 ++- tensorflow/core/kernels/xent_op.h | 35 +- tensorflow/core/kernels/xent_op_gpu.cu.cc | 9 +- tensorflow/core/ops/array_ops.cc | 26 +- tensorflow/core/ops/nn_ops.cc | 23 +- tensorflow/core/ops/nn_ops_test.cc | 16 +- tensorflow/core/public/version.h | 4 +- .../python/contrib.bayesflow.monte_carlo.md | 36 +- .../api_guides/python/contrib.losses.md | 28 +- .../docs_src/community/documentation.md | 38 +- tensorflow/docs_src/install/install_c.md | 2 +- tensorflow/docs_src/install/install_go.md | 2 +- tensorflow/docs_src/install/install_java.md | 22 +- tensorflow/docs_src/install/install_linux.md | 22 +- tensorflow/docs_src/install/install_mac.md | 14 +- .../docs_src/install/install_sources.md | 9 +- tensorflow/docs_src/mobile/optimizing.md | 2 + tensorflow/docs_src/mobile/prepare_models.md | 2 +- tensorflow/python/BUILD | 2 +- .../python/kernel_tests/array_ops_test.py | 26 +- tensorflow/python/kernel_tests/testdata/BUILD | 2 +- .../python/kernel_tests/xent_op_test.py | 81 +++- tensorflow/python/layers/convolutional.py | 2 + .../python/layers/convolutional_test.py | 6 + tensorflow/python/ops/linalg_ops.py | 2 +- .../python/training/monitored_session.py | 33 +- .../python/training/monitored_session_test.py | 36 ++ tensorflow/tensorflow.bzl | 4 +- .../tools/api/golden/tensorflow.train.pbtxt | 2 +- .../tools/ci_build/osx/libtensorflow_cpu.sh | 2 +- tensorflow/tools/docker/Dockerfile.devel | 2 +- .../tools/docker/Dockerfile.devel-cpu-mkl | 2 +- tensorflow/tools/docker/Dockerfile.devel-gpu | 2 +- tensorflow/tools/lib_package/BUILD | 2 - tensorflow/tools/pip_package/BUILD | 1 - tensorflow/tools/pip_package/setup.py | 6 +- tensorflow/workspace.bzl | 133 ++++--- third_party/mkl/BUILD | 46 ++- third_party/mkl/MKL_LICENSE | 201 ++++++++++ third_party/mkl/build_defs.bzl | 12 + third_party/mkl/mkl.BUILD | 27 +- 116 files changed, 1703 insertions(+), 556 deletions(-) create mode 100644 tensorflow/contrib/lite/kernels/maximum.cc create mode 100644 tensorflow/contrib/lite/kernels/maximum_test.cc create mode 100644 third_party/mkl/MKL_LICENSE diff --git a/RELEASE.md b/RELEASE.md index 6f54dee58f..c63d9f20c9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,63 @@ +# Release 1.7.0 + +## Major Features And Improvements +* Eager mode is moving out of contrib, try `tf.enable_eager_execution()`. +* Graph rewrites emulating fixed-point quantization compatible with TensorFlow Lite, supported by new `tf.contrib.quantize` package. +* Easily customize gradient computation with `tf.custom_gradient`. +* [TensorBoard Debugger Plugin](https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/debugger/README.md), the graphical user interface (GUI) of TensorFlow Debugger (tfdbg), is now in alpha. +* Experimental support for reading a sqlite database as a `Dataset` with new `tf.contrib.data.SqlDataset`. +* Distributed Mutex / CriticalSection added to `tf.contrib.framework.CriticalSection`. +* Better text processing with `tf.regex_replace`. +* Easy, efficient sequence input with `tf.contrib.data.bucket_by_sequence_length` + +## Bug Fixes and Other Changes +* Accelerated Linear Algebra (XLA): + * Add `MaxPoolGradGrad` support for XLA + * CSE pass from Tensorflow is now disabled in XLA. +* `tf.data`: + * `tf.data.Dataset` + * Add support for building C++ Dataset op kernels as external libraries, using the `tf.load_op_library()` mechanism. + * `Dataset.list_files()` now shuffles its output by default. + * `Dataset.shuffle(..., seed=tf.constant(0, dtype=tf.int64))` now yields the same sequence of elements as `Dataset.shuffle(..., seed=0)`. + * Add `num_parallel_reads` argument to `tf.data.TFRecordDataset`. +* `tf.contrib`: + * `tf.contrib.bayesflow.halton_sequence` now supports randomization. + * Add support for scalars in `tf.contrib.all_reduce`. + * Add `effective_sample_size` to `tf.contrib.bayesflow.mcmc_diagnostics`. + * Add `potential_scale_reduction` to `tf.contrib.bayesflow.mcmc_diagnostics`. + * Add `BatchNormalization`, `Kumaraswamy` bijectors. + * Deprecate `tf.contrib.learn`. Please check contrib/learn/README.md for instructions on how to convert existing code. + * `tf.contrib.data` + * Remove deprecated `tf.contrib.data.Dataset`, `tf.contrib.data.Iterator`, `tf.contrib.data.FixedLengthRecordDataset`, `tf.contrib.data.TextLineDataset`, and `tf.contrib.data.TFRecordDataset` classes. + * Added `bucket_by_sequence_length`, `sliding_window_batch`, and `make_batched_features_dataset` + * Remove unmaintained `tf.contrib.ndlstm`. You can find it externally at https://github.com/tmbarchive/tfndlstm. + * Moved most of `tf.contrib.bayesflow` to its own repo: `tfp` +* Other: + * tf.py_func now reports the full stack trace if an exception occurs. + * Integrate `TPUClusterResolver` with GKE's integration for Cloud TPUs. + * Add a library for statistical testing of samplers. + * Add Helpers to stream data from the GCE VM to a Cloud TPU. + * Integrate ClusterResolvers with TPUEstimator. + * Unify metropolis_hastings interface with HMC kernel. + * Move LIBXSMM convolutions to a separate --define flag so that they are disabled by default. + * Fix `MomentumOptimizer` lambda. + * Reduce `tfp.layers` boilerplate via programmable docstrings. + * Add `auc_with_confidence_intervals`, a method for computing the AUC and confidence interval with linearithmic time complexity. + * `regression_head` now accepts customized link function, to satisfy the usage that user can define their own link function if the `array_ops.identity` does not meet the requirement. + * Fix `initialized_value` and `initial_value` behaviors for `ResourceVariables` created from `VariableDef` protos. + * Add TensorSpec to represent the specification of Tensors. + * Constant folding pass is now deterministic. + * Support `float16` `dtype` in `tf.linalg.*`. + * Add `tf.estimator.export.TensorServingInputReceiver` that allows `tf.estimator.Estimator.export_savedmodel` to pass raw tensors to model functions. + +## Thanks to our Contributors + +This release contains contributions from many people at Google, as well as: + +4d55397500, Abe, Alistair Low, Andy Kernahan, Appledore, Ben, Ben Barsdell, Boris Pfahringer, Brad Wannow, Brett Koonce, Carl Thomé, cclauss, Chengzhi Chen, Chris Drake, Christopher Yeh, Clayne Robison, Codrut Grosu, Daniel Trebbien, Danny Goodman, David Goodwin, David Norman, Deron Eriksson, Donggeon Lim, Donny Viszneki, DosLin, DylanDmitri, Francisco Guerrero, Fred Reiss, gdh1995, Giuseppe, Glenn Weidner, gracehoney, Guozhong Zhuang, Haichen "Hc" Li, Harald Husum, harumitsu.nobuta, Henry Spivey, hsm207, Jekyll Song, Jerome, Jiongyan Zhang, jjsjann123, John Sungjin Park, Johnson145, JoshVarty, Julian Wolff, Jun Wang, June-One, Kamil Sindi, Kb Sriram, Kdavis-Mozilla, Kenji, lazypanda1, Liang-Chi Hsieh, Loo Rong Jie, Mahesh Bhosale, MandarJKulkarni, ManHyuk, Marcus Ong, Marshal Hayes, Martin Pool, matthieudelaro, mdfaijul, mholzel, Michael Zhou, Ming Li, Minmin Sun, Myungjoo Ham, MyungsungKwak, Naman Kamra, Peng Yu, Penghao Cen, Phil, Raghuraman-K, resec, Rohin Mohanadas, Sandeep N Gupta, Scott Tseng, seaotterman, Seo Sanghyeon, Sergei Lebedev, Ted Chang, terrytangyuan, Tim H, tkunic, Tod, vihanjain, Yan Facai (颜发才), Yin Li, Yong Tang, Yukun Chen, Yusuke Yamada + + + # Release 1.6.0 ## Breaking Changes diff --git a/configure.py b/configure.py index 22b9abedd7..0f52c0ec99 100644 --- a/configure.py +++ b/configure.py @@ -1414,7 +1414,7 @@ def main(): set_build_var(environ_cp, 'TF_NEED_S3', 'Amazon S3 File System', 'with_s3_support', True, 's3') set_build_var(environ_cp, 'TF_NEED_KAFKA', 'Apache Kafka Platform', - 'with_kafka_support', False, 'kafka') + 'with_kafka_support', True, 'kafka') set_build_var(environ_cp, 'TF_ENABLE_XLA', 'XLA JIT', 'with_xla_support', False, 'xla') set_build_var(environ_cp, 'TF_NEED_GDR', 'GDR', 'with_gdr_support', diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 6ab43638ba..29a01efc84 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -240,6 +240,13 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "with_kafka_support_windows_override", + define_values = {"with_kafka_support": "true"}, + values = {"cpu": "x64_windows"}, + visibility = ["//visibility:public"], +) + config_setting( name = "with_gcp_support_android_override", define_values = {"with_gcp_support": "true"}, diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index bdbd738906..fb81b50fe8 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -51,7 +51,6 @@ py_library( "//tensorflow/contrib/image:single_image_random_dot_stereograms_py", "//tensorflow/contrib/input_pipeline:input_pipeline_py", "//tensorflow/contrib/integrate:integrate_py", - "//tensorflow/contrib/kafka", "//tensorflow/contrib/keras", "//tensorflow/contrib/kernel_methods", "//tensorflow/contrib/kfac", @@ -110,7 +109,13 @@ py_library( "//tensorflow/python:util", ] + if_mpi(["//tensorflow/contrib/mpi_collectives:mpi_collectives_py"]) + if_tensorrt([ "//tensorflow/contrib/tensorrt:init_py", - ]), + ]) + select({ + "//tensorflow:with_kafka_support_windows_override": [], + "//tensorflow:with_kafka_support": [ + "//tensorflow/contrib/kafka", + ], + "//conditions:default": [], + }), ) cc_library( @@ -120,7 +125,6 @@ cc_library( "//tensorflow/contrib/boosted_trees:boosted_trees_kernels", "//tensorflow/contrib/coder:all_kernels", "//tensorflow/contrib/data/kernels:dataset_kernels", - "//tensorflow/contrib/kafka:dataset_kernels", "//tensorflow/contrib/factorization/kernels:all_kernels", "//tensorflow/contrib/input_pipeline:input_pipeline_ops_kernels", "//tensorflow/contrib/layers:sparse_feature_cross_op_kernel", @@ -133,7 +137,13 @@ cc_library( "//tensorflow/contrib/text:all_kernels", ] + if_mpi(["//tensorflow/contrib/mpi_collectives:mpi_collectives_py"]) + if_cuda([ "//tensorflow/contrib/nccl:nccl_kernels", - ]), + ]) + select({ + "//tensorflow:with_kafka_support_windows_override": [], + "//tensorflow:with_kafka_support": [ + "//tensorflow/contrib/kafka:dataset_kernels", + ], + "//conditions:default": [], + }), ) cc_library( @@ -146,7 +156,6 @@ cc_library( "//tensorflow/contrib/factorization:all_ops", "//tensorflow/contrib/framework:all_ops", "//tensorflow/contrib/input_pipeline:input_pipeline_ops_op_lib", - "//tensorflow/contrib/kafka:dataset_ops_op_lib", "//tensorflow/contrib/layers:sparse_feature_cross_op_op_lib", "//tensorflow/contrib/nccl:nccl_ops_op_lib", "//tensorflow/contrib/nearest_neighbor:nearest_neighbor_ops_op_lib", @@ -157,7 +166,13 @@ cc_library( "//tensorflow/contrib/tensor_forest:tensor_forest_ops_op_lib", "//tensorflow/contrib/text:all_ops", "//tensorflow/contrib/tpu:all_ops", - ], + ] + select({ + "//tensorflow:with_kafka_support_windows_override": [], + "//tensorflow:with_kafka_support": [ + "//tensorflow/contrib/kafka:dataset_ops_op_lib", + ], + "//conditions:default": [], + }), ) filegroup( diff --git a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc index 0f4c2298f5..0b28f81e7c 100644 --- a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc +++ b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc @@ -253,7 +253,7 @@ class CreateQuantileAccumulatorOp : public OpKernel { private: float epsilon_; int32 num_quantiles_; - // An upperbound on the number of enteries that the summaries might have + // An upper bound on the number of entries that the summaries might have // for a feature. int64 max_elements_; bool generate_quantiles_; diff --git a/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc b/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc index cf4f9a097a..35b059f349 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc @@ -54,7 +54,7 @@ Status BatchFeatures::Initialize( TF_CHECK_AND_RETURN_IF_ERROR( dense_float_feature.dim_size(1) == 1, errors::InvalidArgument( - "Dense float features may not be multi-valent: dim_size(1) = ", + "Dense float features may not be multivalent: dim_size(1) = ", dense_float_feature.dim_size(1))); dense_float_feature_columns_.emplace_back(dense_float_feature); } diff --git a/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc b/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc index 609519e8b1..cfe9101e74 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc @@ -59,7 +59,7 @@ TEST_F(BatchFeaturesTest, DenseFloatFeatures_Multivalent) { BatchFeatures batch_features(1); auto dense_vec = AsTensor({3.0f, 7.0f}, {1, 2}); auto expected_error = InvalidArgument( - "Dense float features may not be multi-valent: dim_size(1) = 2"); + "Dense float features may not be multivalent: dim_size(1) = 2"); EXPECT_EQ(expected_error, batch_features.Initialize({dense_vec}, {}, {}, {}, {}, {}, {})); } diff --git a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc index db34db998a..ce67db797d 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc @@ -54,7 +54,7 @@ Status DropoutUtils::DropOutTrees( if (probability_of_skipping_dropout < 0 || probability_of_skipping_dropout > 1) { return errors::InvalidArgument( - "Probability of skiping dropout must be in [0,1] range"); + "Probability of skipping dropout must be in [0,1] range"); } const auto num_trees = weights.size(); diff --git a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h index 928bfbfe5c..77c16da541 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h +++ b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h @@ -66,7 +66,7 @@ class DropoutUtils { // Current weights and num_updates will be updated as a result of this // func std::vector* current_weights, - // How many weight assignements have been done for each tree already. + // How many weight assignments have been done for each tree already. std::vector* num_updates); }; diff --git a/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc b/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc index 0138aae3db..cc7604745e 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc @@ -34,7 +34,7 @@ TEST_F(SparseColumnIterableTest, Empty) { } TEST_F(SparseColumnIterableTest, Iterate) { - // 8 examples having 7 sparse features with the 3rd and 7th multi-valent. + // 8 examples having 7 sparse features with the 3rd and 7th multivalent. // This can be visualized like the following: // Instance | Sparse | // 0 | x | diff --git a/tensorflow/contrib/boosted_trees/proto/tree_config.proto b/tensorflow/contrib/boosted_trees/proto/tree_config.proto index 4407c4d981..81411aa84a 100644 --- a/tensorflow/contrib/boosted_trees/proto/tree_config.proto +++ b/tensorflow/contrib/boosted_trees/proto/tree_config.proto @@ -53,7 +53,7 @@ message DenseFloatBinarySplit { // Float feature column and split threshold describing // the rule feature <= threshold. int32 feature_column = 1; - // If feature column is multivalent, this holds the index of the dimensiong + // If feature column is multivalent, this holds the index of the dimension // for the split. Defaults to 0. int32 dimension_id = 5; float threshold = 2; diff --git a/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py b/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py index c1acf35160..cf55759aaa 100644 --- a/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py +++ b/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py @@ -120,8 +120,8 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): """Sets up the prediction tests. Create a batch of two examples having one dense float, two sparse float - single valued, one sparse float multidimensionl and one sparse int features. - The data looks like the following: + single valued, one sparse float multidimensional and one sparse int + features. The data looks like the following: | Instance | Dense0 | SparseF0 | SparseF1 | SparseI0 | SparseM | 0 | 7 | -3 | | 9,1 | __, 5.0 | 1 | -2 | | 4 | | 3, ___ @@ -810,7 +810,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): # building. This tree should never be dropped. num_trees = 10 with self.test_session(): - # Empty tree ensenble. + # Empty tree ensemble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() # Add 10 trees with some weights. for i in range(0, num_trees): @@ -951,7 +951,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): def testDropOutZeroProb(self): with self.test_session(): - # Empty tree ensenble. + # Empty tree ensemble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() # Add 1000 trees with some weights. for i in range(0, 999): @@ -994,7 +994,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): def testAveragingAllTrees(self): with self.test_session(): - # Empty tree ensenble. + # Empty tree ensemble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() adjusted_tree_ensemble_config = ( tree_config_pb2.DecisionTreeEnsembleConfig()) diff --git a/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py b/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py index 81f58de28c..074623699d 100644 --- a/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py +++ b/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py @@ -482,7 +482,7 @@ class QuantilesOpTest(test_util.TensorFlowTestCase): """Sets up the quantile op tests. Create a batch of 4 examples having 2 dense and 4 sparse features. - Forth sparse feature is multivalent (3 dimensional) + Fourth sparse feature is multivalent (3 dimensional) The data looks like this | Instance | Dense 0 | Dense 1 | Sparse 0 | Sparse 1 |Sparse 2| SparseM | 0 | -0.1 | -1 | -2 | 0.1 | |_ ,1,_ diff --git a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py index 97d57e8b23..1b184d296b 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py @@ -184,7 +184,7 @@ class QuantileAccumulator(saver.BaseSaverBuilder.SaveableObject): """Finalizes quantile summary stream and resets it for next iteration. Args: - stamp_token: Exepcted current token. + stamp_token: Expected current token. next_stamp_token: Next value for the token. Returns: A list of quantiles or approximate boundaries. diff --git a/tensorflow/contrib/cmake/tf_tests.cmake b/tensorflow/contrib/cmake/tf_tests.cmake index f793877c8b..92f2ab6dea 100644 --- a/tensorflow/contrib/cmake/tf_tests.cmake +++ b/tensorflow/contrib/cmake/tf_tests.cmake @@ -210,6 +210,9 @@ if (tensorflow_BUILD_PYTHON_TESTS) "${tensorflow_source_dir}/tensorflow/contrib/learn/python/learn/learn_io/graph_io_test.py" # Test is flaky on Windows GPU builds (b/38283730). "${tensorflow_source_dir}/tensorflow/contrib/factorization/python/ops/gmm_test.py" + # Disable following manual tag in BUILD. + "${tensorflow_source_dir}/tensorflow/python/keras/_impl/keras/layers/convolutional_test.py" + ) if (WIN32) set(tf_test_src_py_exclude diff --git a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py index 5abb38c2d2..75482f67da 100644 --- a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py @@ -413,6 +413,20 @@ class BatchDatasetTest(test.TestCase): def testMapAndBatchPartialBatchDropRemainder(self): return self._testMapAndBatchPartialBatchHelper(drop_remainder=True) + def testMapAndBatchYieldsPartialBatch(self): + iterator = (dataset_ops.Dataset.range(10) + .apply(batching.map_and_batch( + lambda x: array_ops.reshape(x * x, [1]), 4)) + .make_one_shot_iterator()) + self.assertEqual([None, 1], iterator.output_shapes.as_list()) + next_element = iterator.get_next() + with self.test_session() as sess: + self.assertAllEqual([[0], [1], [4], [9]], sess.run(next_element)) + self.assertAllEqual([[16], [25], [36], [49]], sess.run(next_element)) + self.assertAllEqual([[64], [81]], sess.run(next_element)) + with self.assertRaises(errors.OutOfRangeError): + sess.run(next_element) + def testMapAndBatchSparse(self): def _sparse(i): diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD index 4fba014d6f..80176397c0 100644 --- a/tensorflow/contrib/eager/python/BUILD +++ b/tensorflow/contrib/eager/python/BUILD @@ -270,7 +270,11 @@ cuda_py_test( "//tensorflow/python/eager:test", "//tensorflow/python/keras", ], - tags = ["notsan"], + tags = [ + "no_oss", # b/74395663 + "no_windows", # TODO: needs investigation on Windows + "notsan", + ], ) filegroup( diff --git a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py index 9261823d77..9adf47d505 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py +++ b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py @@ -418,7 +418,6 @@ class SpinnTest(test_util.TensorFlowTestCase): if event.summary.value and event.summary.value[0].tag == "train/loss"] self.assertEqual(config.epochs, len(train_losses)) - self.assertLess(train_losses[-1], train_losses[0]) # 5. Verify that checkpoints exist and contains all the expected variables. self.assertTrue(glob.glob(os.path.join(config.logdir, "ckpt*"))) diff --git a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py index e0fae2c992..fa2697800e 100644 --- a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py +++ b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py @@ -136,7 +136,7 @@ def replicate_model_fn(model_fn, the train_op argument of `EstimatorSpec`. loss_reduction: controls whether losses are summed or averaged. devices: Optional list of devices to replicate the model across. This - argument can be used to replice only on the subset of available GPUs. + argument can be used to replicate only on the subset of available GPUs. If `None`, then all available GPUs are going to be used for replication. If no GPUs are available, then the model is going to be placed on the CPU. diff --git a/tensorflow/contrib/factorization/kernels/clustering_ops.cc b/tensorflow/contrib/factorization/kernels/clustering_ops.cc index dd61f59585..2a6c97e8b9 100644 --- a/tensorflow/contrib/factorization/kernels/clustering_ops.cc +++ b/tensorflow/contrib/factorization/kernels/clustering_ops.cc @@ -353,7 +353,7 @@ class NearestNeighborsOp : public OpKernel { auto worker_threads = *(context->device()->tensorflow_cpu_worker_threads()); const int64 num_threads = worker_threads.num_threads; // This kernel might be configured to use fewer than the total number of - // available CPUs on the host machine. To avoid descructive interference + // available CPUs on the host machine. To avoid destructive interference // with other jobs running on the host machine, we must only use a fraction // of total available L3 cache. Unfortunately, we cannot query the host // machine to get the number of physical CPUs. So, we use a fixed per-CPU diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops.py b/tensorflow/contrib/factorization/python/ops/factorization_ops.py index 054888e734..8e0ed1d80e 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops.py @@ -106,7 +106,7 @@ class WALSModel(object): # the prep_gramian_op for row(column) can be run. worker_init_op = model.worker_init - # To be run once per interation sweep before the row(column) update + # To be run once per integration sweep before the row(column) update # initialize ops can be run. Note that in the distributed training # situations, this should only be run by the chief trainer. All other # trainers need to block until this is done. @@ -118,9 +118,9 @@ class WALSModel(object): init_row_update_op = model.initialize_row_update_op init_col_update_op = model.initialize_col_update_op - # Ops to upate row(column). This can either take the entire sparse tensor - # or slices of sparse tensor. For distributed trainer, each trainer - # handles just part of the matrix. + # Ops to update row(column). This can either take the entire sparse + # tensor or slices of sparse tensor. For distributed trainer, each + # trainer handles just part of the matrix. _, row_update_op, unreg_row_loss, row_reg, _ = model.update_row_factors( sp_input=matrix_slices_from_queue_for_worker_shard) row_loss = unreg_row_loss + row_reg @@ -220,7 +220,7 @@ class WALSModel(object): in the form of [[w_0, w_1, ...], [w_k, ... ], [...]], with the number of inner lists matching the number of row factor shards and the elements in each inner list are the weights for the rows of the corresponding row - factor shard. In this case, w_ij = unonbserved_weight + + factor shard. In this case, w_ij = unobserved_weight + row_weights[i] * col_weights[j]. - If this is a single non-negative real number, this value is used for all row weights and w_ij = unobserved_weight + row_weights * @@ -435,7 +435,7 @@ class WALSModel(object): gramian: Variable storing the gramian calculated from the factors. Returns: - A op that updates the gramian with the calcuated value from the factors. + A op that updates the gramian with the calculated value from the factors. """ partial_gramians = [] for f in factors: @@ -564,7 +564,7 @@ class WALSModel(object): Note that specifically this initializes the cache of the row and column weights on workers when `use_factors_weights_cache` is True. In this case, - if these weights are being calcualted and reset after the object is created, + if these weights are being calculated and reset after the object is created, it is important to ensure this ops is run afterwards so the cache reflects the correct values. """ diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py index c813733915..bb5140aeb3 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py @@ -210,7 +210,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reprodue the same row factors in the model as the + # This is expected to reproduce the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -283,8 +283,8 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 3 column feature vectors. - # This is expected to reprodue the same column factors in the model as the - # weights and feature vectors are identical to that used in model + # This is expected to reproduce the same column factors in the model as + # the weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( sp_input=sp_feeder, @@ -385,7 +385,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reprodue the same row factors in the model as the + # This is expected to reproduce the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -462,8 +462,8 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 2 column feature vectors. - # This is expected to reprodue the same column factors in the model as the - # weights and feature vectors are identical to that used in model + # This is expected to reproduce the same column factors in the model as + # the weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( sp_input=sp_feeder, diff --git a/tensorflow/contrib/factorization/python/ops/gmm_ops.py b/tensorflow/contrib/factorization/python/ops/gmm_ops.py index 98d6434f47..14d4c733e3 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_ops.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_ops.py @@ -280,7 +280,7 @@ class GmmAlgorithm(object): self._define_score_samples() def _define_full_covariance_probs(self, shard_id, shard): - """Defines the full covariance probabilties per example in a class. + """Defines the full covariance probabilities per example in a class. Updates a matrix with dimension num_examples X num_classes. @@ -344,7 +344,7 @@ class GmmAlgorithm(object): def _define_prior_log_prob_operation(self, shard_id): """Computes the prior probability of all samples. - Updates a vector where each item is the prior probabibility of an + Updates a vector where each item is the prior probability of an input example. Args: diff --git a/tensorflow/contrib/factorization/python/ops/gmm_test.py b/tensorflow/contrib/factorization/python/ops/gmm_test.py index 00a4734eb6..4fc9c96e9d 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_test.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_test.py @@ -210,7 +210,7 @@ class GMMTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependendent on inputs, if input + # Note that since cluster initialization is dependent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/kmeans_test.py b/tensorflow/contrib/factorization/python/ops/kmeans_test.py index 0103cc4439..88eb9cf692 100644 --- a/tensorflow/contrib/factorization/python/ops/kmeans_test.py +++ b/tensorflow/contrib/factorization/python/ops/kmeans_test.py @@ -413,7 +413,7 @@ class KMeansCosineDistanceTest(KMeansTestBase): self.assertAllClose(score, self.true_score, atol=1e-2) def test_predict_kmeans_plus_plus(self): - # Most points are concetrated near one center. KMeans++ is likely to find + # Most points are concentrated near one center. KMeans++ is likely to find # the less populated centers. points = np.array( [[2.5, 3.5], [2.5, 3.5], [-2, 3], [-2, 3], [-3, -3], [-3.1, -3.2], @@ -604,7 +604,7 @@ class KMeansTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependendent on inputs, if input + # Note that since cluster initialization is dependent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/wals.py b/tensorflow/contrib/factorization/python/ops/wals.py index 4fe22ea26e..62db3bb4c4 100644 --- a/tensorflow/contrib/factorization/python/ops/wals.py +++ b/tensorflow/contrib/factorization/python/ops/wals.py @@ -235,7 +235,7 @@ def _wals_factorization_model_function(features, labels, mode, params): num_items: An integer, the total number of items of this axis. update_fn: A function that takes one argument (`sp_input`), and that returns a tuple of - * new_factors: A flot Tensor of the factor values after update. + * new_factors: A float Tensor of the factor values after update. * update_op: a TensorFlow op which updates the factors. * loss: A float Tensor, the unregularized loss. * reg_loss: A float Tensor, the regularization loss. diff --git a/tensorflow/contrib/learn/BUILD b/tensorflow/contrib/learn/BUILD index 9c59150580..16f80a876f 100644 --- a/tensorflow/contrib/learn/BUILD +++ b/tensorflow/contrib/learn/BUILD @@ -226,6 +226,7 @@ py_test( size = "small", srcs = ["python/learn/monitors_test.py"], srcs_version = "PY2AND3", + tags = ["no_pip_gpu"], # b/74437598 deps = [ ":learn", "//tensorflow/contrib/framework:framework_py", diff --git a/tensorflow/contrib/learn/python/learn/estimators/linear.py b/tensorflow/contrib/learn/python/learn/estimators/linear.py index 64d7ecc68e..70b70af98c 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/linear.py +++ b/tensorflow/contrib/learn/python/learn/estimators/linear.py @@ -243,8 +243,8 @@ def sdca_model_fn(features, labels, mode, params): parent_scope = "linear" - with variable_scope.variable_op_scope( - features.values(), parent_scope) as scope: + with variable_scope.variable_scope( + values=features.values(), name_or_scope=parent_scope) as scope: features = features.copy() features.update(layers.transform_features(features, feature_columns)) logits, columns_to_variables, bias = ( diff --git a/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py b/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py index 05794a42c5..d4e54c82f9 100644 --- a/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py +++ b/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py @@ -140,8 +140,8 @@ def sdca_model_fn(features, labels, mode, params, config=None): parent_scope = "linear" - with variable_scope.variable_op_scope(features.values(), - parent_scope) as scope: + with variable_scope.variable_scope( + values=features.values(), name_or_scope=parent_scope) as scope: features = features.copy() features.update(layers.transform_features(features, feature_columns)) logits, columns_to_variables, bias = ( diff --git a/tensorflow/contrib/lite/README.md b/tensorflow/contrib/lite/README.md index 2680d515eb..c15ae3f233 100644 --- a/tensorflow/contrib/lite/README.md +++ b/tensorflow/contrib/lite/README.md @@ -126,6 +126,9 @@ The above pre-trained models have been trained on the ImageNet data set, which c The [TensorFlow for Poets](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets/) codelab walks through this process step-by-step. The retraining code supports retraining for both floating point and quantized inference. +# Getting started with RaspberryPi + +Using RaspberryPi can be accomplished by following the [Makefile instructions](g3doc/rpi.md). That will give a you a static library (.a) that you can build your app against. Python bindings will be coming soon as well as a demo app. ### Train a custom model A developer may choose to train a custom model using Tensorflow. TensorFlow documentation has [several tutorials](https://www.tensorflow.org/tutorials/) for building and training models. If the user has written a model using TensorFlow's Slim Framework the first step is to export this to a GraphDef file. This is necessary because Slim does not store the model structure outside the code, so to communicate with other parts of the framework it needs to be exported. Documentation for the export can be found [here](https://github.com/tensorflow/models/tree/master/research/slim#Export). The output of this step will be a .pb file for the custom model. diff --git a/tensorflow/contrib/lite/builtin_ops.h b/tensorflow/contrib/lite/builtin_ops.h index d7993e60cc..17b791e4e2 100644 --- a/tensorflow/contrib/lite/builtin_ops.h +++ b/tensorflow/contrib/lite/builtin_ops.h @@ -79,6 +79,7 @@ typedef enum { kTfLiteBuiltinBidirectionalSequenceLstm = 52, kTfLiteBuiltinCast = 53, kTfLiteBuiltinPrelu = 54, + kTfLiteBuiltinMaximum = 55, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/contrib/lite/g3doc/models.md b/tensorflow/contrib/lite/g3doc/models.md index 5b393140d6..48f43d4fc4 100644 --- a/tensorflow/contrib/lite/g3doc/models.md +++ b/tensorflow/contrib/lite/g3doc/models.md @@ -1,4 +1,4 @@ -#List of Hosted Models +# List of Hosted Models * [Inception V3 2015](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_2015_2017_11_10.zip) * [Inception V3 Slim 2016](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) diff --git a/tensorflow/contrib/lite/kernels/BUILD b/tensorflow/contrib/lite/kernels/BUILD index 1450c1e14b..c423c00bf5 100644 --- a/tensorflow/contrib/lite/kernels/BUILD +++ b/tensorflow/contrib/lite/kernels/BUILD @@ -156,6 +156,7 @@ cc_library( "local_response_norm.cc", "lsh_projection.cc", "lstm.cc", + "maximum.cc", "mean.cc", "mfcc.cc", "mul.cc", @@ -536,6 +537,18 @@ tf_cc_test( ], ) +tf_cc_test( + name = "maximum_test", + size = "small", + srcs = ["maximum_test.cc"], + deps = [ + ":builtin_ops", + "//tensorflow/contrib/lite:framework", + "//tensorflow/contrib/lite/kernels:test_util", + "@com_google_googletest//:gtest", + ], +) + tf_cc_test( name = "mean_test", size = "small", diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h index 33d60afa26..3575974ae9 100644 --- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h @@ -404,6 +404,7 @@ inline void DepthToSpace(const T* input_data, const Dims<4>& input_dims, const int in_d = out_d + ((out_h % block_size) * block_size + out_w % block_size) * output_depth; + const int in_w = out_w / block_size; const int in_h = out_h / block_size; const int in_b = out_b; @@ -3363,6 +3364,30 @@ void TensorFlowMaximum(const T* input1_data, const Dims<4>& input1_dims, } } +template +void TensorFlowMaximum(const T* input1_data, const Dims<4>& input1_dims, + const T* input2_data, const Dims<4>& input2_dims, + T* output_data, const Dims<4>& output_dims) { + NdArrayDesc<4> desc1; + NdArrayDesc<4> desc2; + NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); + + for (int b = 0; b < ArraySize(output_dims, 3); ++b) { + for (int y = 0; y < ArraySize(output_dims, 2); ++y) { + for (int x = 0; x < ArraySize(output_dims, 1); ++x) { + for (int c = 0; c < ArraySize(output_dims, 0); ++c) { + auto out_idx = Offset(output_dims, c, x, y, b); + auto in1_idx = SubscriptToIndex(desc1, c, x, y, b); + auto in2_idx = SubscriptToIndex(desc2, c, x, y, b); + auto in1_val = input1_data[in1_idx]; + auto in2_val = input2_data[in2_idx]; + output_data[out_idx] = in1_val > in2_val ? in1_val : in2_val; + } + } + } + } +} + template void ArgMax(const T3* axis, const T1* input_data, const Dims<4>& input_dims, T2* output_data, const Dims<4>& output_dims) { diff --git a/tensorflow/contrib/lite/kernels/maximum.cc b/tensorflow/contrib/lite/kernels/maximum.cc new file mode 100644 index 0000000000..9fdf2b47ea --- /dev/null +++ b/tensorflow/contrib/lite/kernels/maximum.cc @@ -0,0 +1,106 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include +#include "tensorflow/contrib/lite/builtin_op_data.h" +#include "tensorflow/contrib/lite/context.h" +#include "tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/contrib/lite/kernels/internal/tensor.h" +#include "tensorflow/contrib/lite/kernels/kernel_util.h" +#include "tensorflow/contrib/lite/kernels/op_macros.h" + +namespace tflite { +namespace ops { +namespace builtin { +namespace maximum { + +// This file has a reference implemenation of TFMaximum. +enum KernelType { + kReference, +}; + +constexpr int kInputTensor1 = 0; +constexpr int kInputTensor2 = 1; +constexpr int kOutputTensor = 0; + +struct MaximumContext { + MaximumContext(TfLiteContext* context, TfLiteNode* node) { + input1 = GetInput(context, node, kInputTensor1); + input2 = GetInput(context, node, kInputTensor2); + output = GetOutput(context, node, kOutputTensor); + } + TfLiteTensor* input1; + TfLiteTensor* input2; + TfLiteTensor* output; +}; + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + + MaximumContext op_context(context, node); + TF_LITE_ENSURE_EQ(context, op_context.input1->type, op_context.input2->type); + TfLiteIntArray* output_dims = TfLiteIntArrayCopy(op_context.input2->dims); + op_context.output->type = op_context.input2->type; + return context->ResizeTensor(context, op_context.output, output_dims); +} + +template +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + MaximumContext op_context(context, node); + +#define TF_LITE_MAXIMUM(kernel_type, data_type) \ + kernel_type::TensorFlowMaximum( \ + GetTensorData(op_context.input1), \ + GetTensorDims(op_context.input1), \ + GetTensorData(op_context.input2), \ + GetTensorDims(op_context.input2), \ + GetTensorData(op_context.output), \ + GetTensorDims(op_context.output)) + + if (kernel_type == kReference) { + switch (op_context.output->type) { + case kTfLiteFloat32: + TF_LITE_MAXIMUM(reference_ops, float); + break; + default: + context->ReportError(context, + "Type %d is currently not supported by Maximum.", + op_context.output->type); + return kTfLiteError; + } + } else { + context->ReportError(context, + "Type %d is currently not supported by Maximum.", + op_context.output->type); + return kTfLiteError; + } +#undef TF_LITE_MAXIMUM + return kTfLiteOk; +} + +} // namespace maximum + +TfLiteRegistration* Register_MAXIMUM_REF() { + static TfLiteRegistration r = {nullptr, nullptr, maximum::Prepare, + maximum::Eval}; + return &r; +} + +TfLiteRegistration* Register_MAXIMUM() { return Register_MAXIMUM_REF(); } + +} // namespace builtin +} // namespace ops +} // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/maximum_test.cc b/tensorflow/contrib/lite/kernels/maximum_test.cc new file mode 100644 index 0000000000..b3fd7d4e6f --- /dev/null +++ b/tensorflow/contrib/lite/kernels/maximum_test.cc @@ -0,0 +1,81 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include "tensorflow/contrib/lite/interpreter.h" +#include "tensorflow/contrib/lite/kernels/register.h" +#include "tensorflow/contrib/lite/kernels/test_util.h" +#include "tensorflow/contrib/lite/model.h" + +namespace tflite { +namespace { + +using ::testing::ElementsAreArray; + +class MaximumOpModel : public SingleOpModel { + public: + MaximumOpModel(const TensorData& input1, const TensorData& input2, + const TensorType& output) { + input1_ = AddInput(input1); + input2_ = AddInput(input2); + output_ = AddOutput(output); + SetBuiltinOp(BuiltinOperator_MAXIMUM, BuiltinOptions_MaximumOptions, + CreateMaximumOptions(builder_).Union()); + BuildInterpreter({GetShape(input1_), GetShape(input2_)}); + } + + template + void SetInput1(std::initializer_list data) { + PopulateTensor(input1_, data); + } + + template + void SetInput2(std::initializer_list data) { + PopulateTensor(input2_, data); + } + + template + std::vector GetOutput() { + return ExtractVector(output_); + } + std::vector GetOutputShape() { return GetTensorShape(output_); } + + protected: + int input1_; + int input2_; + int output_; +}; + +TEST(MaximumOpTest, FloatTest) { + std::initializer_list data1 = {1.0, 0.0, -1.0, 11.0, -2.0, -1.44}; + std::initializer_list data2 = {-1.0, 0.0, 1.0, 12.0, -3.0, -1.43}; + MaximumOpModel m({TensorType_FLOAT32, {3, 1, 2}}, + {TensorType_FLOAT32, {3, 1, 2}}, TensorType_FLOAT32); + m.SetInput1(data1); + m.SetInput2(data2); + m.Invoke(); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1, 2})); + EXPECT_THAT( + m.GetOutput(), + ElementsAreArray(ArrayFloatNear({1.0, 0.0, 1.0, 12.0, -2.0, -1.43}))); +} + +} // namespace +} // namespace tflite + +int main(int argc, char** argv) { + ::tflite::LogToStderr(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tensorflow/contrib/lite/kernels/register.cc b/tensorflow/contrib/lite/kernels/register.cc index 62045f0a4d..0f98154b90 100644 --- a/tensorflow/contrib/lite/kernels/register.cc +++ b/tensorflow/contrib/lite/kernels/register.cc @@ -76,6 +76,7 @@ TfLiteRegistration* Register_LOG_SOFTMAX(); TfLiteRegistration* Register_CAST(); TfLiteRegistration* Register_DEQUANTIZE(); TfLiteRegistration* Register_PRELU(); +TfLiteRegistration* Register_MAXIMUM(); BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_RELU, Register_RELU()); @@ -133,6 +134,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_CAST, Register_CAST()); AddBuiltin(BuiltinOperator_DEQUANTIZE, Register_DEQUANTIZE()); AddBuiltin(BuiltinOperator_PRELU, Register_PRELU()); + AddBuiltin(BuiltinOperator_MAXIMUM, Register_MAXIMUM()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc index b7ccdf070b..791d1378f3 100644 --- a/tensorflow/contrib/lite/model.cc +++ b/tensorflow/contrib/lite/model.cc @@ -597,6 +597,9 @@ void* ParseOpData(const Operator* op, BuiltinOperator op_type, builtin_data = reinterpret_cast(params); break; } + case BuiltinOperator_MAXIMUM: { + break; + } case BuiltinOperator_DELEGATE: { // TODO(ycling): Revisit when supporting saving delegated models. error_reporter->Report("DELEGATE op shouldn't exist in model."); diff --git a/tensorflow/contrib/lite/nnapi_delegate.cc b/tensorflow/contrib/lite/nnapi_delegate.cc index e31b7c03a5..decaf9f160 100644 --- a/tensorflow/contrib/lite/nnapi_delegate.cc +++ b/tensorflow/contrib/lite/nnapi_delegate.cc @@ -350,6 +350,7 @@ void AddOpsAndParams(tflite::Interpreter* interpreter, case tflite::BuiltinOperator_DELEGATE: case tflite::BuiltinOperator_CAST: case tflite::BuiltinOperator_PRELU: + case tflite::BuiltinOperator_MAXIMUM: FATAL("Op code %d is currently not delegated to NNAPI", builtin); nn_op_type = -1; // set to invalid break; diff --git a/tensorflow/contrib/lite/python/lite.py b/tensorflow/contrib/lite/python/lite.py index 35d224924e..ed6dd036f9 100644 --- a/tensorflow/contrib/lite/python/lite.py +++ b/tensorflow/contrib/lite/python/lite.py @@ -25,9 +25,9 @@ EXPERIMENTAL: APIs here are unstable and likely to change without notice. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os -import subprocess -import tempfile +import os as _os +import subprocess as _subprocess +import tempfile as _tempfile # pylint: disable=unused-import from tensorflow.contrib.lite.python.op_hint import convert_op_hints_to_stubs @@ -74,7 +74,7 @@ else: _toco_from_proto_bin = _resource_loader.get_path_to_datafile( "../toco/python/toco_from_protos") -if _toco_from_proto_bin and not os.path.exists(_toco_from_proto_bin): +if _toco_from_proto_bin and not _os.path.exists(_toco_from_proto_bin): _toco_from_proto_bin = "toco_from_protos" @@ -102,10 +102,10 @@ def toco_convert_protos(model_flags_str, toco_flags_str, input_data_str): return _toco_python.TocoConvert( model_flags_str, toco_flags_str, input_data_str) - with tempfile.NamedTemporaryFile() as fp_toco, \ - tempfile.NamedTemporaryFile() as fp_model, \ - tempfile.NamedTemporaryFile() as fp_input, \ - tempfile.NamedTemporaryFile() as fp_output: + with _tempfile.NamedTemporaryFile() as fp_toco, \ + _tempfile.NamedTemporaryFile() as fp_model, \ + _tempfile.NamedTemporaryFile() as fp_input, \ + _tempfile.NamedTemporaryFile() as fp_output: fp_model.write(model_flags_str) fp_toco.write(toco_flags_str) fp_input.write(input_data_str) @@ -118,11 +118,11 @@ def toco_convert_protos(model_flags_str, toco_flags_str, input_data_str): fp_output.name ] cmdline = " ".join(cmd) - proc = subprocess.Popen( + proc = _subprocess.Popen( cmdline, shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stdout=_subprocess.PIPE, + stderr=_subprocess.STDOUT, close_fds=True) stdout, stderr = proc.communicate() exitcode = proc.returncode diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs index e1075971e9..7d2e00fe32 100644 --- a/tensorflow/contrib/lite/schema/schema.fbs +++ b/tensorflow/contrib/lite/schema/schema.fbs @@ -131,6 +131,7 @@ enum BuiltinOperator : byte { BIDIRECTIONAL_SEQUENCE_LSTM = 52, CAST = 53, PRELU = 54, + MAXIMUM = 55, } // Options for the builtin operators. @@ -173,6 +174,7 @@ union BuiltinOptions { LogSoftmaxOptions, CastOptions, DequantizeOptions, + MaximumOptions, } enum Padding : byte { SAME, VALID } @@ -384,6 +386,9 @@ table CastOptions { table DequantizeOptions { } +table MaximumOptions { +} + // An OperatorCode can be an enum value (BuiltinOperator) if the operator is a // builtin, or a string if the operator is custom. table OperatorCode { diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h index 86daeaf5cc..66a97a1460 100755 --- a/tensorflow/contrib/lite/schema/schema_generated.h +++ b/tensorflow/contrib/lite/schema/schema_generated.h @@ -145,6 +145,9 @@ struct CastOptionsT; struct DequantizeOptions; struct DequantizeOptionsT; +struct MaximumOptions; +struct MaximumOptionsT; + struct OperatorCode; struct OperatorCodeT; @@ -255,11 +258,12 @@ enum BuiltinOperator { BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM = 52, BuiltinOperator_CAST = 53, BuiltinOperator_PRELU = 54, + BuiltinOperator_MAXIMUM = 55, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_PRELU + BuiltinOperator_MAX = BuiltinOperator_MAXIMUM }; -inline BuiltinOperator (&EnumValuesBuiltinOperator())[53] { +inline BuiltinOperator (&EnumValuesBuiltinOperator())[54] { static BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -313,7 +317,8 @@ inline BuiltinOperator (&EnumValuesBuiltinOperator())[53] { BuiltinOperator_DELEGATE, BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM, BuiltinOperator_CAST, - BuiltinOperator_PRELU + BuiltinOperator_PRELU, + BuiltinOperator_MAXIMUM }; return values; } @@ -375,6 +380,7 @@ inline const char **EnumNamesBuiltinOperator() { "BIDIRECTIONAL_SEQUENCE_LSTM", "CAST", "PRELU", + "MAXIMUM", nullptr }; return names; @@ -425,11 +431,12 @@ enum BuiltinOptions { BuiltinOptions_LogSoftmaxOptions = 36, BuiltinOptions_CastOptions = 37, BuiltinOptions_DequantizeOptions = 38, + BuiltinOptions_MaximumOptions = 39, BuiltinOptions_MIN = BuiltinOptions_NONE, - BuiltinOptions_MAX = BuiltinOptions_DequantizeOptions + BuiltinOptions_MAX = BuiltinOptions_MaximumOptions }; -inline BuiltinOptions (&EnumValuesBuiltinOptions())[39] { +inline BuiltinOptions (&EnumValuesBuiltinOptions())[40] { static BuiltinOptions values[] = { BuiltinOptions_NONE, BuiltinOptions_Conv2DOptions, @@ -469,7 +476,8 @@ inline BuiltinOptions (&EnumValuesBuiltinOptions())[39] { BuiltinOptions_SplitOptions, BuiltinOptions_LogSoftmaxOptions, BuiltinOptions_CastOptions, - BuiltinOptions_DequantizeOptions + BuiltinOptions_DequantizeOptions, + BuiltinOptions_MaximumOptions }; return values; } @@ -515,6 +523,7 @@ inline const char **EnumNamesBuiltinOptions() { "LogSoftmaxOptions", "CastOptions", "DequantizeOptions", + "MaximumOptions", nullptr }; return names; @@ -681,6 +690,10 @@ template<> struct BuiltinOptionsTraits { static const BuiltinOptions enum_value = BuiltinOptions_DequantizeOptions; }; +template<> struct BuiltinOptionsTraits { + static const BuiltinOptions enum_value = BuiltinOptions_MaximumOptions; +}; + struct BuiltinOptionsUnion { BuiltinOptions type; void *value; @@ -1016,6 +1029,14 @@ struct BuiltinOptionsUnion { return type == BuiltinOptions_DequantizeOptions ? reinterpret_cast(value) : nullptr; } + MaximumOptionsT *AsMaximumOptions() { + return type == BuiltinOptions_MaximumOptions ? + reinterpret_cast(value) : nullptr; + } + const MaximumOptionsT *AsMaximumOptions() const { + return type == BuiltinOptions_MaximumOptions ? + reinterpret_cast(value) : nullptr; + } }; bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *obj, BuiltinOptions type); @@ -3759,6 +3780,46 @@ inline flatbuffers::Offset CreateDequantizeOptions( flatbuffers::Offset CreateDequantizeOptions(flatbuffers::FlatBufferBuilder &_fbb, const DequantizeOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct MaximumOptionsT : public flatbuffers::NativeTable { + typedef MaximumOptions TableType; + MaximumOptionsT() { + } +}; + +struct MaximumOptions FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef MaximumOptionsT NativeTableType; + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + verifier.EndTable(); + } + MaximumOptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(MaximumOptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct MaximumOptionsBuilder { + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + explicit MaximumOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + MaximumOptionsBuilder &operator=(const MaximumOptionsBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateMaximumOptions( + flatbuffers::FlatBufferBuilder &_fbb) { + MaximumOptionsBuilder builder_(_fbb); + return builder_.Finish(); +} + +flatbuffers::Offset CreateMaximumOptions(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct OperatorCodeT : public flatbuffers::NativeTable { typedef OperatorCode TableType; BuiltinOperator builtin_code; @@ -3990,6 +4051,9 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const DequantizeOptions *builtin_options_as_DequantizeOptions() const { return builtin_options_type() == BuiltinOptions_DequantizeOptions ? static_cast(builtin_options()) : nullptr; } + const MaximumOptions *builtin_options_as_MaximumOptions() const { + return builtin_options_type() == BuiltinOptions_MaximumOptions ? static_cast(builtin_options()) : nullptr; + } const flatbuffers::Vector *custom_options() const { return GetPointer *>(VT_CUSTOM_OPTIONS); } @@ -4168,6 +4232,10 @@ template<> inline const DequantizeOptions *Operator::builtin_options_as inline const MaximumOptions *Operator::builtin_options_as() const { + return builtin_options_as_MaximumOptions(); +} + struct OperatorBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; @@ -5696,6 +5764,29 @@ inline flatbuffers::Offset CreateDequantizeOptions(flatbuffer _fbb); } +inline MaximumOptionsT *MaximumOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = new MaximumOptionsT(); + UnPackTo(_o, _resolver); + return _o; +} + +inline void MaximumOptions::UnPackTo(MaximumOptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; +} + +inline flatbuffers::Offset MaximumOptions::Pack(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateMaximumOptions(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateMaximumOptions(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const MaximumOptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + return tflite::CreateMaximumOptions( + _fbb); +} + inline OperatorCodeT *OperatorCode::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = new OperatorCodeT(); UnPackTo(_o, _resolver); @@ -6028,6 +6119,10 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } + case BuiltinOptions_MaximumOptions: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } default: return false; } } @@ -6198,6 +6293,10 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); } + case BuiltinOptions_MaximumOptions: { + auto ptr = reinterpret_cast(obj); + return ptr->UnPack(resolver); + } default: return nullptr; } } @@ -6356,6 +6455,10 @@ inline flatbuffers::Offset BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff auto ptr = reinterpret_cast(value); return CreateDequantizeOptions(_fbb, ptr, _rehasher).Union(); } + case BuiltinOptions_MaximumOptions: { + auto ptr = reinterpret_cast(value); + return CreateMaximumOptions(_fbb, ptr, _rehasher).Union(); + } default: return 0; } } @@ -6514,6 +6617,10 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL value = new DequantizeOptionsT(*reinterpret_cast(u.value)); break; } + case BuiltinOptions_MaximumOptions: { + value = new MaximumOptionsT(*reinterpret_cast(u.value)); + break; + } default: break; } @@ -6711,6 +6818,11 @@ inline void BuiltinOptionsUnion::Reset() { delete ptr; break; } + case BuiltinOptions_MaximumOptions: { + auto ptr = reinterpret_cast(value); + delete ptr; + break; + } default: break; } value = nullptr; diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index 555ea90034..12b7b3c350 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -36,6 +36,7 @@ gen_zipped_test_files( "local_response_norm.zip", "log_softmax.zip", "max_pool.zip", + "maximum.zip", "mean.zip", "mul.zip", "pad.zip", diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py index cb5c500136..8045052452 100644 --- a/tensorflow/contrib/lite/testing/generate_examples.py +++ b/tensorflow/contrib/lite/testing/generate_examples.py @@ -862,6 +862,41 @@ def make_log_softmax_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +def make_maximum_tests(zip_path): + """Make a set of tests to do maximum.""" + + test_parameters = [{ + "input_dtype": [tf.float32], + "input_shape_1": [[3], [1, 100], [4, 2, 3], [5, 224, 224, 3]], + "input_shape_2": [[3], [1, 100], [4, 2, 3], [5, 224, 224, 3]], + }] + + def build_graph(parameters): + """Build the maximum op testing graph.""" + input_tensor_1 = tf.placeholder( + dtype=parameters["input_dtype"], + name="input_1", + shape=parameters["input_shape_1"]) + input_tensor_2 = tf.placeholder( + dtype=parameters["input_dtype"], + name="input_2", + shape=parameters["input_shape_2"]) + + out = tf.maximum(input_tensor_1, input_tensor_2) + return [input_tensor_1, input_tensor_2], [out] + + def build_inputs(parameters, sess, inputs, outputs): + values = [ + create_tensor_data(parameters["input_dtype"], + parameters["input_shape_1"]), + create_tensor_data(parameters["input_dtype"], + parameters["input_shape_2"]) + ] + return values, sess.run(outputs, feed_dict=dict(zip(inputs, values))) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + def make_binary_op_tests_func(binary_operator): """Return a function that does a test on a binary operator.""" return lambda zip_path: make_binary_op_tests(zip_path, binary_operator) @@ -1977,6 +2012,7 @@ def main(unused_args): "exp.zip": make_exp_tests, "log_softmax.zip": make_log_softmax_tests, "lstm.zip": make_lstm_tests, + "maximum.zip": make_maximum_tests, } out = FLAGS.zip_to_output bin_path = FLAGS.toco diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc index a4a7283508..6697b86e79 100644 --- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc @@ -253,6 +253,7 @@ INSTANTIATE_TESTS(l2_pool) INSTANTIATE_TESTS(l2norm) INSTANTIATE_TESTS(local_response_norm) INSTANTIATE_TESTS(log_softmax) +INSTANTIATE_TESTS(maximum) INSTANTIATE_TESTS(max_pool) INSTANTIATE_TESTS(mean) INSTANTIATE_TESTS(mul) diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc index f23249cfa1..0989bfe5a3 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator.cc @@ -863,6 +863,8 @@ std::vector> BuildOperatorList() { ops.emplace_back(new SimpleOperator("EXP", OperatorType::kExp)); ops.emplace_back(new SimpleOperator( "LOG_SOFTMAX", OperatorType::kLogSoftmax)); + ops.emplace_back(new SimpleOperator( + "MAXIMUM", OperatorType::kTensorFlowMaximum)); return ops; } diff --git a/tensorflow/contrib/lite/toco/tflite/operator_test.cc b/tensorflow/contrib/lite/toco/tflite/operator_test.cc index 9c19f8d464..f7a213ecfc 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator_test.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator_test.cc @@ -109,6 +109,8 @@ TEST_F(OperatorTest, SimpleOperators) { CheckSimpleOperator("EXP", OperatorType::kExp); CheckSimpleOperator("LOG_SOFTMAX", OperatorType::kLogSoftmax); + CheckSimpleOperator( + "MAXIMUM", OperatorType::kTensorFlowMaximum); } TEST_F(OperatorTest, BuiltinAdd) { diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index a57a1e5421..a03e731be3 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -494,7 +494,7 @@ class MutableDenseHashTable(LookupInterface): value_dtype=tf.int64, default_value=-1, empty_key=0) - table.insert(keys, values) + sess.run(table.insert(keys, values)) out = table.lookup(query_keys) print(out.eval()) ``` diff --git a/tensorflow/contrib/makefile/download_dependencies.sh b/tensorflow/contrib/makefile/download_dependencies.sh index 4ae18b2cef..8b415e6527 100755 --- a/tensorflow/contrib/makefile/download_dependencies.sh +++ b/tensorflow/contrib/makefile/download_dependencies.sh @@ -34,7 +34,7 @@ PROTOBUF_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/protobuf/. RE2_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/re2/.*tar\.gz' "${BZL_FILE_PATH}" | head -n1)" FFT2D_URL="$(grep -o 'http.*fft\.tgz' "${BZL_FILE_PATH}" | grep -v mirror.bazel | head -n1)" ABSL_URL="$(grep -o 'https://github.com/abseil/abseil-cpp/.*tar.gz' "${BZL_FILE_PATH}" | head -n1)" -CUB_URL="$(grep -o 'https.*cub/archive.*zip' "${BZL_FILE_PATH}" | grep -v bazel-mirror | head -n1)" +CUB_URL="$(grep -o 'https.*cub/archive.*zip' "${BZL_FILE_PATH}" | grep -v mirror.bazel | head -n1)" # TODO(petewarden): Some new code in Eigen triggers a clang bug with iOS arm64, # so work around it by patching the source. diff --git a/tensorflow/contrib/makefile/tf_op_files.txt b/tensorflow/contrib/makefile/tf_op_files.txt index 5a812af4e9..7a7683c953 100644 --- a/tensorflow/contrib/makefile/tf_op_files.txt +++ b/tensorflow/contrib/makefile/tf_op_files.txt @@ -258,6 +258,7 @@ tensorflow/core/kernels/requantize.cc tensorflow/core/kernels/remote_fused_graph_execute_op.cc tensorflow/core/kernels/remote_fused_graph_execute_utils.cc tensorflow/core/kernels/batch_matmul_op_real.cc +tensorflow/core/kernels/random_op.cc tensorflow/core/ops/training_ops.cc tensorflow/core/ops/string_ops.cc tensorflow/core/ops/state_ops.cc diff --git a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc index dfa12e873a..a9a32b7b25 100644 --- a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc +++ b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc @@ -74,7 +74,7 @@ class GatherTreeOp : public OpKernel { ctx, step_ids_shape.dim_size(1) == max_sequence_lengths.shape().dim_size(0), errors::InvalidArgument("batch size dimensions step_ids.shape[1] and " - "max_seqeuence_lengths.shape[0] must match. " + "max_sequence_lengths.shape[0] must match. " "but shapes are: ", step_ids_shape.DebugString(), " and ", max_sequence_lengths.shape().DebugString())); diff --git a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py index 9ff8a343f1..be53779826 100644 --- a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py +++ b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py @@ -736,7 +736,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): """Base attention mechanism for monotonic attention. Simply overrides the initial_alignments function to provide a dirac - distribution,which is needed in order for the monotonic attention + distribution, which is needed in order for the monotonic attention distributions to have the correct behavior. """ @@ -763,7 +763,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Bahadanau-style energy function. - This type of attention encorces a monotonic constraint on the attention + This type of attention enforces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -867,7 +867,7 @@ class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Luong-style energy function. - This type of attention encorces a monotonic constraint on the attention + This type of attention enforces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -1133,7 +1133,7 @@ class AttentionWrapper(rnn_cell_impl.RNNCell): output_attention: Python bool. If `True` (default), the output at each time step is the attention value. This is the behavior of Luong-style attention mechanisms. If `False`, the output at each time step is - the output of `cell`. This is the beahvior of Bhadanau-style + the output of `cell`. This is the behavior of Bhadanau-style attention mechanisms. In both cases, the `attention` tensor is propagated to the next time step via the state and is used there. This flag only controls whether the attention mechanism is propagated diff --git a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py index a26107b0d7..184144f64a 100644 --- a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py +++ b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py @@ -821,9 +821,9 @@ def _get_scores(log_probs, sequence_lengths, length_penalty_weight): Returns: The scores normalized by the length_penalty. """ - length_penality_ = _length_penalty( + length_penalty_ = _length_penalty( sequence_lengths=sequence_lengths, penalty_factor=length_penalty_weight) - return log_probs / length_penality_ + return log_probs / length_penalty_ def _length_penalty(sequence_lengths, penalty_factor): @@ -860,7 +860,7 @@ def _mask_probs(probs, eos_token, finished): unfinished beams remain unchanged. Args: - probs: Log probabiltiies of shape `[batch_size, beam_width, vocab_size]` + probs: Log probabilities of shape `[batch_size, beam_width, vocab_size]` eos_token: An int32 id corresponding to the EOS token to allocate probability to. finished: A boolean tensor of shape `[batch_size, beam_width]` that diff --git a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py index b3343aef47..99ad487630 100644 --- a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py +++ b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py @@ -115,8 +115,8 @@ class ParallelReader(io_ops.ReaderBase): reader needs to start reading from a new file since it has finished with the previous file). - A queue runner for enqueing in the `common_queue` is automatically added to - the TF QueueRunners collection. + A queue runner for enqueuing in the `common_queue` is automatically added + to the TF QueueRunners collection. Args: queue: A Queue or a mutable string Tensor representing a handle diff --git a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py index 37e9c4754c..62bd200361 100644 --- a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py +++ b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py @@ -36,9 +36,9 @@ def prefetch_queue(tensors, dynamic_pad=False, shared_name=None, name=None): - """Creates a queue to prefetech tensors from `tensors`. + """Creates a queue to prefetch tensors from `tensors`. - A queue runner for enqueing tensors into the prefetch_queue is automatically + A queue runner for enqueuing tensors into the prefetch_queue is automatically added to the TF QueueRunners collection. Example: diff --git a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py index b3b61e1dfe..f2d31dc8db 100644 --- a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py +++ b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py @@ -124,7 +124,7 @@ class BoundingBox(ItemHandler): super(BoundingBox, self).__init__(self._full_keys) def tensors_to_item(self, keys_to_tensors): - """Maps the given dictionary of tensors to a contatenated list of bboxes. + """Maps the given dictionary of tensors to a concatenated list of bboxes. Args: keys_to_tensors: a mapping of TF-Example keys to parsed tensors. diff --git a/tensorflow/contrib/tensorrt/README.md b/tensorflow/contrib/tensorrt/README.md index 461e627e99..6eafc1754c 100644 --- a/tensorflow/contrib/tensorrt/README.md +++ b/tensorflow/contrib/tensorrt/README.md @@ -1,15 +1,15 @@ -Using TensorRT in TensorFlow -============================ +# Using TensorRT in TensorFlow + This module provides necessary bindings and introduces TRT_engine_op operator that wraps a subgraph in TensorRT. This is still a work in progress but should be useable with most common graphs. -Compilation ------------ +## Compilation + In order to compile the module, you need to have a local TensorRT -installation (libnvinfer.so and respective include files). During the +installation ( libnvinfer.so and respective include files ). During the configuration step, TensorRT should be enabled and installation path should be set. If installed through package managers (deb,rpm), configure script should find the necessary components from the system @@ -22,4 +22,38 @@ bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/ ``` After the installation of tensorflow package, TensorRT transformation -will be available. An example use can be found in test/test_tftrt.py directory +will be available. An example use can be found in test/test_tftrt.py script + +## Installing TensorRT 3.0.4 + +In order to make use of TensorRT integration, you will need a local installation of TensorRT 3.0.4 from the [NVIDIA Developer website](https://developer.nvidia.com/tensorrt). Due to compiler compatibility, you will need to download and install the TensorRT 3.0.4 tarball for _Ubuntu 14.04_, i.e., **_TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz_**, even if you are using Ubuntu 16.04 or later. + +### Preparing TensorRT installation + +Once you have downloaded TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz, you will need to unpack it to an installation directory, which will be referred to as . Please replace with the full path of actual installation directory you choose in commands below. + +```shell +cd && tar -zxf /path/to/TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz +``` + +After unpacking the binaries, you have several options to use them: + +#### To run TensorFlow as a user without superuser privileges + +For a regular user without any sudo rights, you should add TensorRT to your `$LD_LIBRARY_PATH`: + + ```shell + export LD_LIBRARY_PATH=/TensorRT-3.0.4/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + ``` + +Then you are ready to use TensorFlow-TensorRT integration. `$LD_LIBRARY_PATH` must contain the path to TensorRT installation for TensorFlow-TensorRT integration to work. If you are using a VirtualEnv-like setup, you can add the command above to your `bin/activate` script or to your `.bashrc` script. + +#### To run TensorFlow as a superuser + + When running as a superuser, such as in a container or via sudo, the `$LD_LIBRARY_PATH` approach above may not work. The following is preferred when the user has superuser privileges: + + ```shell + echo "/TensorRT-3.0.4/lib" | sudo tee /etc/ld.so.conf.d/tensorrt304.conf && sudo ldconfig + ``` + + Please ensure that any existing deb package installation of TensorRT is removed before following these instructions to avoid package conflicts. \ No newline at end of file diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index eea8c8efa2..ff8cc6374d 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -49,12 +49,13 @@ namespace tensorrt { namespace convert { namespace { -bool IsTensorRTCandidate(const tensorflow::NodeDef& node_def) { +bool IsTensorRTCandidate(const tensorflow::Node* node) { // LINT.IfChange // TODO(jie): Segmentation shouldn't associated with op name. // Split it into a registration for each kernel. static const std::set candidate_ops = { "Identity", + "Snapshot", "Const", "Conv2D", "MaxPool", @@ -74,7 +75,7 @@ bool IsTensorRTCandidate(const tensorflow::NodeDef& node_def) { // TODO(ben,jie): ... }; // LINT.ThenChange(//tensorflow/contrib/tensorrt/convert/convert_nodes.h) - return candidate_ops.count(node_def.op()); + return candidate_ops.count(node->type_string()); } void GetSubGraphIncomingEdges(const tensorflow::Graph& graph, @@ -84,10 +85,10 @@ void GetSubGraphIncomingEdges(const tensorflow::Graph& graph, const tensorflow::Node* node = graph.FindNodeId(node_id); for (const tensorflow::Edge* edge : node->in_edges()) { if (!subgraph_node_ids.count(edge->src()->id()) && - !edge->src()->IsSource()) { + !edge->src()->IsSource() && !edge->IsControlEdge()) { incoming_edges->insert(edge); } else { - VLOG(2) << edge->src()->name() << " N, "; + VLOG(2) << node->name() << " -> " << edge->src()->name() << " N, "; } } } @@ -100,11 +101,11 @@ void GetSubGraphOutgoingEdges(const tensorflow::Graph& graph, const tensorflow::Node* node = graph.FindNodeId(node_id); for (const tensorflow::Edge* edge : node->out_edges()) { if (!subgraph_node_ids.count(edge->dst()->id()) && - !edge->dst()->IsSink()) { - VLOG(2) << edge->dst()->name() << " Y, "; + !edge->dst()->IsSink() && !edge->IsControlEdge()) { + VLOG(2) << node->name() << " -> " << edge->dst()->name() << " Y, "; outgoing_edges->insert(edge); } else { - VLOG(2) << edge->dst()->name() << " N, "; + VLOG(2) << node->name() << " -> " << edge->dst()->name() << " N, "; } } } @@ -409,8 +410,9 @@ tensorflow::Status ConvertGraphDefToTensorRT( tensorflow::Status status = ConvertSubGraphToTensorRT(&p); if (status != tensorflow::Status::OK()) { LOG(WARNING) << "subgraph conversion error for subgraph_index:" << count - << " due to: \n" - << status.ToString() << " SKIPPING......"; + << " due to: \"" << status.ToString() + << "\" SKIPPING......( " << subgraph_node_names.size() + << " nodes)"; } count++; } diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 92a692baa7..370911e4d9 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -53,8 +53,8 @@ limitations under the License. namespace tensorflow { namespace tensorrt { namespace convert { +using ::tensorflow::strings::StrAppend; using ::tensorflow::strings::StrCat; - namespace { inline tensorflow::Status ConvertDType(tensorflow::DataType tf_dtype, @@ -430,9 +430,8 @@ class Converter { tensorflow::tensorrt::TRTWeightStore* weight_store_; bool fp16_; void register_op_converters(); - std::vector get_inputs( - const tensorflow::NodeDef& node_def) { - std::vector inputs; + tensorflow::Status get_inputs(const tensorflow::NodeDef& node_def, + std::vector* inputs) { for (auto const& input_name : node_def.input()) { /************************************************************************* * TODO(jie) handle case 1) here @@ -453,13 +452,17 @@ class Converter { VLOG(2) << "retrieve input: " << name; if (trt_tensors_.count(name)) { - inputs.push_back(trt_tensors_.at(name)); + inputs->push_back(trt_tensors_.at(name)); } else { - LOG(FATAL) << "input: " << name << " not available for node at, " - << node_def.name(); + string str("Node "); + StrAppend(&str, node_def.name(), " should have an input named '", name, + "' but it is not available"); + LOG(WARNING) << "input: " << name << " not available for node at " + << node_def.name(); + return tensorflow::errors::InvalidArgument(str); } } - return inputs; + return tensorflow::Status::OK(); } public: @@ -483,7 +486,8 @@ class Converter { } tensorflow::Status convert_node(const tensorflow::NodeDef& node_def) { - std::vector inputs = this->get_inputs(node_def); + std::vector inputs; + TF_RETURN_IF_ERROR(this->get_inputs(node_def, &inputs)); string op = node_def.op(); if (!op_registry_.count(op)) { return tensorflow::errors::Unimplemented( @@ -548,6 +552,19 @@ class Converter { } }; +TRT_ShapedWeights ConvertFP32ToFP16(Converter& ctx, + const TRT_ShapedWeights& weights_src) { + auto dtype_new = tensorflow::DataType::DT_HALF; + TRT_ShapedWeights weights = + ctx.get_temp_weights(dtype_new, weights_src.shape_); + const float* src = static_cast(weights_src.GetValues()); + Eigen::half* dst = const_cast( + static_cast(weights.GetValues())); + for (int64_t i = 0; i < weights_src.count(); i++) { + dst[i] = Eigen::half_impl::float_to_half_rtne(src[i]); + } + return weights; +} // **************************************************************************** // Constant folding functions // TODO(jie): once optimizer kicks in, we should have done constant folding @@ -875,7 +892,7 @@ tensorflow::Status BinaryTensorOpWeight( // Check type consistency nvinfer1::DataType ttype; - TF_CHECK_OK(ConvertDType(weights.type_, &ttype)); + TF_RETURN_IF_ERROR(ConvertDType(weights.type_, &ttype)); // Check scale mode auto dims_w = weights.shape_; @@ -957,6 +974,10 @@ tensorflow::Status BinaryTensorOpWeight( } } + if (ctx.isFP16()) { + weights = ConvertFP32ToFP16(ctx, weights); + } + // prepare weights TRT_ShapedWeights shift_weights(weights.type_); TRT_ShapedWeights scale_weights(weights.type_); @@ -998,9 +1019,7 @@ enum class ConvolutionType { DEFAULT, DEPTHWISE_CONV }; tensorflow::Status ConvertConv2DHelper( Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, - std::vector* outputs, - int group // group ==0 specifies depthwise conv -) { + std::vector* outputs, int group) { const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); TFAttrs attrs(node_def); @@ -1025,6 +1044,10 @@ tensorflow::Status ConvertConv2DHelper( VLOG(2) << "groups count: " << num_groups; TRT_ShapedWeights weights_rsck = inputs.at(1).weights(); + if (ctx.isFP16()) { + weights_rsck = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); + } + TRT_ShapedWeights weights = ctx.get_temp_weights_like(weights_rsck); ReorderRSCKToKCRS(weights_rsck, &weights, num_groups); TRT_ShapedWeights biases(weights.type_); @@ -1134,9 +1157,9 @@ tensorflow::Status BinaryTensorOpTensor( CHECK_EQ_TYPE(tensor_r->getType(), dtype); auto op_pair = ops.find(node_def.op()); if (op_pair == ops.end()) - return tensorflow::errors::Unimplemented("binary op: " + node_def.op() + - " not supported at: " + - node_def.name()); + return tensorflow::errors::Unimplemented( + "binary op: " + node_def.op() + + " not supported at: " + node_def.name()); nvinfer1::IElementWiseLayer* layer = ctx.network()->addElementWise( *const_cast(tensor_l), @@ -1295,8 +1318,11 @@ tensorflow::Status ConvertScale(Converter& ctx, // Implement tensor binaryOp weight [channel wise] for now; const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); - // TODO(jie): handle NHWC/NCHW transpose; TRT_ShapedWeights weights = inputs.at(1).weights(); + if (ctx.isFP16()) { + weights = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); + } + TRT_ShapedWeights empty_weights(weights.type_); TFAttrs attrs(node_def); @@ -1376,8 +1402,11 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.d[0] = weights_tensor.float_val_size(); scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; } else { - LOG(FATAL) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); + LOG(WARNING) << "Broadcast on weights only supports kCHANNEL and" + << " kUNIFORM, at: " << node_def.name(); + string err_str("Broadcast method is not supported for '"); + StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); + return tensorflow::errors::InvalidArgument(err_str); } } } else { @@ -1391,33 +1420,16 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - if (ctx.isFP16()) { - auto dtype_new = tensorflow::DataType::DT_HALF; - size_t len_data = tensorflow::DataTypeSize(dtype_new); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); - auto half_tensor = temp_tensor.flat(); - Eigen::DefaultDevice defd; - half_tensor.device(defd) = - tensor.flat().template cast(); - memcpy(dst, half_tensor.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); - } else { - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.float_val().begin(), - weights_tensor.float_val() - .end()); // make a local copy first to flatten - memcpy(dst, tensor_data.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.float_val().begin(), + weights_tensor.float_val() + .end()); // make a local copy first to flatten + memcpy(dst, tensor_data.data(), len_data); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); } else if (!weights_tensor.int_val().empty()) { VLOG(2) << "int!!!" << node_def.name(); nvinfer1::Dims scalar_shape; @@ -1432,8 +1444,11 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.d[0] = weights_tensor.int_val_size(); scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; } else { - LOG(FATAL) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); + LOG(WARNING) << "Broadcast on weights only supports kCHANNEL and" + << " kUNIFORM, at: " << node_def.name(); + string err_str("Broadcast method is not supported for '"); + StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); + return tensorflow::errors::InvalidArgument(err_str); } } } else { @@ -1447,62 +1462,23 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - if (ctx.isFP16()) { - auto dtype_new = tensorflow::DataType::DT_HALF; - size_t len_data = tensorflow::DataTypeSize(dtype_new); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); - TTypes::Flat half_tensor = temp_tensor.flat(); - Eigen::DefaultDevice defd; - switch (dtype) { - case (tensorflow::DT_INT32): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_INT16): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_INT8): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - case (tensorflow::DT_UINT8): { - half_tensor.device(defd) = - tensor.flat().template cast(); - break; - } - default: - return tensorflow::errors::InvalidArgument( - "Datatype " + tensorflow::DataTypeString(dtype) + - " for FP16 conversion"); - break; - }; - memcpy(dst, half_tensor.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); - } else { - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) - len_data *= scalar_shape.d[i]; - size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); - len_data = std::max(len_data, len_tensor); - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.int_val().begin(), - weights_tensor.int_val() - .end()); // make a local copy first to flatten - // doesn't have to be contiguous - memcpy(dst, tensor_data.data(), len_tensor); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } + // we should not have converted //if (ctx.isFP16()) { + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; + size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); + len_data = std::max(len_data, len_tensor); + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.int_val().begin(), + weights_tensor.int_val().end()); // make a local copy first to flatten + // doesn't have to be contigous + memcpy(dst, tensor_data.data(), len_tensor); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); } else if (!weights_tensor.tensor_content().empty()) { + // obsolete method. + // After optimization path, we do not see weights in this format. + // fp16 conversion technically should be needed here. VLOG(2) << "TENSOR!!!" << node_def.name(); const auto& content = weights_tensor.tensor_content(); @@ -1784,8 +1760,6 @@ tensorflow::Status ConvertConcat(Converter& ctx, TRT_ShapedWeights axis = inputs.at(input_size).weights(); TFAttrs attrs(node_def); - // auto attr_size = attrs.at("N")->i(); - // auto data_type = attrs.get("T"); auto index_type = attrs.get("Tidx"); // TODO(jie): handle data type @@ -1875,71 +1849,103 @@ tensorflow::Status ConvertFusedBatchNorm( "only is_training=false is supported, at " + node_def.name()); } nvinfer1::ITensor const* tensor = inputs.at(0).tensor(); - TRT_ShapedWeights scale_weights = inputs.at(1).weights(); - TRT_ShapedWeights offset_weights = inputs.at(2).weights(); - TRT_ShapedWeights mean_weights = inputs.at(3).weights(); - TRT_ShapedWeights variance_weights = inputs.at(4).weights(); - TRT_ShapedWeights dummy_power_weights(scale_weights.type_); - TRT_ShapedWeights combined_scale_weights = - ctx.get_temp_weights_like(scale_weights); - TRT_ShapedWeights combined_offset_weights = - ctx.get_temp_weights_like(offset_weights); - size_t nweight = scale_weights.count(); - if ((scale_weights.type_ == offset_weights.type_) && - (mean_weights.type_ == variance_weights.type_) && - (scale_weights.type_ == variance_weights.type_)) { - if ((scale_weights.type_ != tensorflow::DataType::DT_FLOAT) && - (scale_weights.type_ != tensorflow::DataType::DT_HALF)) { + + // Check parameter types + auto parameter_type = inputs.at(1).weights().type_; + if ((parameter_type != tensorflow::DataType::DT_FLOAT) && + (parameter_type != tensorflow::DataType::DT_HALF)) { + return tensorflow::errors::Unimplemented( + "only float32 or float16 weight data type is supported, for node " + + node_def.name() + " got " + tensorflow::DataTypeString(parameter_type)); + } + for (int i = 1; i < 5; i++) { + if (inputs.at(i).weights().type_ != parameter_type) { return tensorflow::errors::Unimplemented( - "only float32 or float16 weight data type is supported, for node " + - node_def.name() + " got " + - tensorflow::DataTypeString(scale_weights.type_)); + "Inconsistent parameter type for batchnormis not supported, at: " + + node_def.name()); } - if (scale_weights.type_ == tensorflow::DT_FLOAT) { - for (size_t i = 0; i < nweight; ++i) { - float scale = (static_cast(scale_weights.GetValues()))[i]; - float offset = - (static_cast(offset_weights.GetValues()))[i]; - float mean = (static_cast(mean_weights.GetValues()))[i]; - float variance = - (static_cast(variance_weights.GetValues()))[i]; - float& combined_scale_ref = const_cast( - static_cast(combined_scale_weights.GetValues()))[i]; - float& combined_offset_ref = const_cast( - static_cast(combined_offset_weights.GetValues()))[i]; - combined_scale_ref = scale / sqrtf(variance + epsilon); - combined_offset_ref = offset - mean * combined_scale_ref; - } - } else { - const Eigen::half* scale_vals = - (static_cast(scale_weights.GetValues())); - const Eigen::half* off_vals = - (static_cast(offset_weights.GetValues())); - const Eigen::half* mean_vals = - (static_cast(mean_weights.GetValues())); - const Eigen::half* variance_vals = - (static_cast(variance_weights.GetValues())); - Eigen::half* comb_scale_vals = const_cast( - static_cast(combined_scale_weights.GetValues())); - Eigen::half* comb_off_vals = const_cast( - static_cast(combined_offset_weights.GetValues())); - for (size_t i = 0; i < nweight; ++i) { - float scale(scale_vals[i]); - float offset(off_vals[i]); - float mean(mean_vals[i]); - float variance(variance_vals[i]); - float combined_scale_ref = scale / sqrtf(variance + epsilon); - comb_scale_vals[i] = Eigen::half(combined_scale_ref); - float combined_offset_ref = offset - mean * combined_scale_ref; - comb_off_vals[i] = Eigen::half(combined_offset_ref); + } + + TRT_ShapedWeights dummy_power_weights(parameter_type); + size_t nweight = 0; + for (int i = 1; i < 5; i++) { + nweight = std::max(nweight, (size_t)inputs.at(i).weights().count()); + } + TRT_ShapedWeights* ptr_shape_weights = nullptr; + for (int i = 1; i < 5; i++) { + if (inputs.at(i).weights().count() == nweight) { + ptr_shape_weights = + const_cast(&(inputs.at(i).weights())); + } else if (inputs.at(i).weights().count() != 1) { + return tensorflow::errors::InvalidArgument( + "Inconsistent batchnorm parameter count, at: " + node_def.name()); + } + } + // We could technically have two weights with different shape. + // that requires two addScale op, arguably less performant + TRT_ShapedWeights combined_scale_weights = + ctx.get_temp_weights_like(*ptr_shape_weights); + TRT_ShapedWeights combined_offset_weights = + ctx.get_temp_weights_like(*ptr_shape_weights); + + const Eigen::half* cast_vals_array[4]; + const float* vals_array[4]; + for (int j = 0; j < 4; j++) { + cast_vals_array[j] = + static_cast(inputs.at(j + 1).weights().GetValues()); + vals_array[j] = + static_cast(inputs.at(j + 1).weights().GetValues()); + } + Eigen::half* cast_combined_scale_vals = const_cast( + static_cast(combined_scale_weights.GetValues())); + Eigen::half* cast_combined_offset_vals = const_cast( + static_cast(combined_offset_weights.GetValues())); + float* combined_scale_vals = const_cast( + static_cast(combined_scale_weights.GetValues())); + float* combined_offset_vals = const_cast( + static_cast(combined_offset_weights.GetValues())); + + for (size_t i = 0; i < nweight; ++i) { + float batchnorm_data[4]; + for (int j = 0; j < 4; j++) { + if (inputs.at(j + 1).weights().count() != 1) { + if (parameter_type == tensorflow::DT_FLOAT) { + batchnorm_data[j] = vals_array[j][i]; + } else if (parameter_type == tensorflow::DT_HALF) { + batchnorm_data[j] = + Eigen::half_impl::half_to_float(cast_vals_array[j][i]); + } + } else { + if (parameter_type == tensorflow::DT_FLOAT) { + batchnorm_data[j] = vals_array[j][0]; + } else if (parameter_type == tensorflow::DT_HALF) { + batchnorm_data[j] = + Eigen::half_impl::half_to_float(cast_vals_array[j][0]); + } } } + float scale = batchnorm_data[0]; + float offset = batchnorm_data[1]; + float mean = batchnorm_data[2]; + float variance = batchnorm_data[3]; + float combined_scale_val = scale / sqrtf(variance + epsilon); + float combined_offset_val = offset - mean * combined_scale_val; + if (parameter_type == tensorflow::DT_FLOAT) { + combined_scale_vals[i] = combined_scale_val; + combined_offset_vals[i] = combined_offset_val; + } else if (parameter_type == tensorflow::DT_HALF) { + cast_combined_scale_vals[i] = Eigen::half(combined_scale_val); + cast_combined_offset_vals[i] = Eigen::half(combined_offset_val); + } } - nvinfer1::IScaleLayer* layer = ctx.network()->addScale( - *const_cast(tensor), nvinfer1::ScaleMode::kCHANNEL, - combined_offset_weights.GetWeightsForTRT(), - combined_scale_weights.GetWeightsForTRT(), - dummy_power_weights.GetWeightsForTRT()); + + nvinfer1::ScaleMode mode = nweight == 1 ? nvinfer1::ScaleMode::kUNIFORM + : nvinfer1::ScaleMode::kCHANNEL; + nvinfer1::IScaleLayer* layer = + ctx.network()->addScale(*const_cast(tensor), mode, + combined_offset_weights.GetWeightsForTRT(), + combined_scale_weights.GetWeightsForTRT(), + dummy_power_weights.GetWeightsForTRT()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); @@ -2050,6 +2056,7 @@ void Converter::register_op_converters() { op_registry_["Const"] = ConvertConst; // TODO(ben,jie): this is a temp hack. op_registry_["Identity"] = ConvertIdentity; // Identity should be removed + op_registry_["Snapshot"] = ConvertIdentity; // Snapshot should be removed // resnet_50_v1 slim implementation op_registry_["Add"] = ConvertBinary; @@ -2143,8 +2150,11 @@ tensorflow::Status ConvertCalibrationNodeToEngineNode( calib_res->thr_->join(); delete calib_res->thr_; if (!calib_res->engine_) { - LOG(FATAL) << "Calibration failed!, engine is nullptr. Did you run " + LOG(ERROR) << "Calibration failed!, engine does not exist. Did you run " "calibration graph?"; + return tensorflow::errors::FailedPrecondition( + "Calibration graph needs to be executed on" + " calibration data before convertsion to inference graph"); } auto weight_rmgr = trt_rm->getManager("WeightStore"); TF_CHECK_OK(weight_rmgr->Delete( @@ -2181,7 +2191,7 @@ tensorflow::Status ConvertCalibrationNodeToEngineNode( return status; } auto trt_engine_node = graph.AddNode(engine_node, &status); - TF_CHECK_OK(status); + TF_RETURN_IF_ERROR(status); for (size_t i = 0; i < out_edges.size(); i++) { VLOG(1) << "Connecting trt_engine_node output " << i << " with " << out_edges.at(i)->dst()->name() << " port " @@ -2279,6 +2289,12 @@ tensorflow::Status InjectCalibrationNode(tensorrt::convert::SubGraphParams& s) { input_dtypes.push_back(tf_dtype); nvinfer1::DataType dtype(nvinfer1::DataType::kFLOAT); + auto type_status = ConvertDType(tf_dtype, &dtype); + if (type_status != tensorflow::Status::OK()) { + LOG(WARNING) << "Data type conversion for input '" << node_name + << "' failed"; + return type_status; + } TF_CHECK_OK(ConvertDType(tf_dtype, &dtype)); VLOG(2) << "accessing output index of: " << output_idx @@ -2346,8 +2362,8 @@ tensorflow::Status InjectCalibrationNode(tensorrt::convert::SubGraphParams& s) { output_names.push_back(tensor_name); auto tensor_or_weights = converter.get_tensor(tensor_name); if (!tensor_or_weights.is_tensor()) { - return tensorflow::errors::InvalidArgument( - "Output node is weights not tensor"); + return tensorflow::errors::InvalidArgument("Output node'" + tensor_name + + "' is weights not tensor"); } nvinfer1::ITensor* tensor = tensor_or_weights.tensor(); if (!tensor) { @@ -2504,7 +2520,11 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( input_dtypes.push_back(tf_dtype); nvinfer1::DataType dtype(nvinfer1::DataType::kFLOAT); - TF_CHECK_OK(ConvertDType(tf_dtype, &dtype)); + auto type_status = ConvertDType(tf_dtype, &dtype); + if (type_status != tensorflow::Status::OK()) { + LOG(WARNING) << "Type conversion failed for " << node_name; + return type_status; + } VLOG(2) << "Accessing output index of: " << output_idx << ", at node: " << node_name @@ -2515,8 +2535,12 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( // TODO(jie): TRT 3.x only support 4 dimensional input tensor. // update the code once TRT 4.0 comes out. - if (op_info.shape().dim_size() != 4) - return tensorflow::errors::Unimplemented("require 4 dimensional input"); + if (op_info.shape().dim_size() != 4) { + string err_str = "Require 4 dimensional input."; + StrAppend(&err_str, " Got ", op_info.shape().dim_size(), " ", + shape_inference_node_name); + return tensorflow::errors::Unimplemented(err_str); + } for (int i = 1; i < op_info.shape().dim_size(); i++) { VLOG(2) << "dimension: " << i @@ -2577,8 +2601,8 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( output_names.push_back(tensor_name); auto tensor_or_weights = converter.get_tensor(tensor_name); if (!tensor_or_weights.is_tensor()) { - return tensorflow::errors::InvalidArgument( - "Output node is weights not tensor"); + return tensorflow::errors::InvalidArgument("Output node '" + tensor_name + + "' is weights not tensor"); } nvinfer1::ITensor* tensor = tensor_or_weights.tensor(); if (!tensor) { @@ -2622,7 +2646,8 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( } TF_RETURN_IF_ERROR(weight_rmgr->Delete( engine_name, engine_name)); - LOG(INFO) << "finished engine " << engine_name; + LOG(INFO) << "finished engine " << engine_name << " containing " + << s.subgraph_node_ids.size() << " nodes"; // Build the TRT op tensorflow::NodeDefBuilder op_builder(engine_name, "TRTEngineOp"); diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 6193f0b0a1..8fc4697c51 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -80,13 +80,20 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, std::vector in_edges(dst->in_edges().begin(), dst->in_edges().end()); for (const tensorflow::Edge* in_edge : in_edges) { - if (in_edge->src() != src) { - tensorflow::Edge* e = const_cast(in_edge); - if (e->src() == graph->source_node()) { - graph->AddEdge(e->src(), e->src_output(), src, - tensorflow::Graph::kControlSlot); - } else { - graph->AddEdge(e->src(), e->src_output(), src, 0 /* input index */); + if (in_edge->IsControlEdge()) { + if (in_edge->src() != src) { + tensorflow::Edge* e = const_cast(in_edge); + graph->AddControlEdge(e->src(), src); + } + } else { + if (in_edge->src() != src) { + tensorflow::Edge* e = const_cast(in_edge); + if (e->src() == graph->source_node()) { + graph->AddEdge(e->src(), e->src_output(), src, + tensorflow::Graph::kControlSlot); + } else { + graph->AddEdge(e->src(), e->src_output(), src, 0 /* input index */); + } } } } @@ -94,12 +101,19 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, std::vector out_edges(dst->out_edges().begin(), dst->out_edges().end()); for (const tensorflow::Edge* out_edge : out_edges) { - tensorflow::Edge* e = const_cast(out_edge); - if (e->dst() == graph->sink_node()) { - graph->AddEdge(src, tensorflow::Graph::kControlSlot, e->dst(), - e->dst_input()); + if (out_edge->IsControlEdge()) { + tensorflow::Edge* e = const_cast(out_edge); + graph->AddControlEdge(src, e->dst()); } else { - graph->AddEdge(src, 0 /* output index */, e->dst(), e->dst_input()); + tensorflow::Edge* e = const_cast(out_edge); + if (e->dst() == graph->sink_node()) { + VLOG(1) << " edge to sink node " << src->name() << " -> " + << e->dst()->name(); + graph->AddEdge(src, tensorflow::Graph::kControlSlot, e->dst(), + e->dst_input()); + } else { + graph->AddEdge(src, 0 /* output index */, e->dst(), e->dst_input()); + } } } @@ -118,7 +132,7 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, tensorflow::Status SegmentGraph( const tensorflow::GraphDef& gdef, - const std::function& candidate_fn, + const std::function& candidate_fn, const SegmentOptions& options, SegmentNodesVector* segments) { // Create a Graph representation of the GraphDef. tensorflow::FunctionLibraryDefinition flib(tensorflow::OpRegistry::Global(), @@ -136,7 +150,7 @@ tensorflow::Status SegmentGraph( for (int i = 0; i < graph.num_node_ids(); ++i) { tensorflow::Node* node = graph.FindNodeId(i); if (options.exclude_node_list.count(node->name()) != 0 || - !candidate_fn(node->def())) { + !candidate_fn(node)) { node = nullptr; } node_segments.emplace_back(node); @@ -155,7 +169,7 @@ tensorflow::Status SegmentGraph( for (const tensorflow::Node* node : order) { // All output nodes of 'node' have been visited... - VLOG(2) << "Trying node " << node->name(); + VLOG(2) << "Trying node " << node->name() << " id=" << node->id(); // 'node' must be a TRT candidate... if (node_segments[node->id()].Value() == nullptr) { @@ -169,8 +183,12 @@ tensorflow::Status SegmentGraph( while (true) { std::set contract_edges; for (const tensorflow::Edge* out_edge : node->out_edges()) { - VLOG(2) << "... out node " << out_edge->dst()->name(); - + VLOG(2) << "... out node " << out_edge->dst()->name() << " ( " + << out_edge->dst()->id() << " <- " << node->id() << " )"; + if (out_edge->IsControlEdge()) { + VLOG(2) << "... ... Control Edge, Skipping"; + continue; + } // Out node must be TRT candidate... if (node_segments[out_edge->dst()->id()].Value() == nullptr) { VLOG(2) << "... ... not a TRT candidate"; @@ -196,7 +214,8 @@ tensorflow::Status SegmentGraph( const tensorflow::Node* src = contract_edge->src(); const tensorflow::Node* dst = contract_edge->dst(); - VLOG(2) << "Merge " << src->name() << " <- " << dst->name(); + VLOG(2) << "Merge " << src->name() << " <- " << dst->name() << " (" + << src->id() << " <- " << dst->id(); node_segments[src->id()].Merge(&node_segments[dst->id()]); // Contracting the edge leaves disconnected graph edges. diff --git a/tensorflow/contrib/tensorrt/segment/segment.h b/tensorflow/contrib/tensorrt/segment/segment.h index ee6e2b3ed2..7e8685f44a 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.h +++ b/tensorflow/contrib/tensorrt/segment/segment.h @@ -20,10 +20,12 @@ limitations under the License. #include #include "tensorflow/core/framework/graph.pb.h" +#include "tensorflow/core/graph/graph.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" namespace tensorflow { + namespace tensorrt { namespace segment { @@ -46,7 +48,7 @@ struct SegmentOptions { // @return the status. tensorflow::Status SegmentGraph( const tensorflow::GraphDef& gdef, - const std::function& candidate_fn, + const std::function& candidate_fn, const SegmentOptions& options, SegmentNodesVector* segments); } // namespace segment diff --git a/tensorflow/contrib/tensorrt/segment/segment_test.cc b/tensorflow/contrib/tensorrt/segment/segment_test.cc index 74cbc5f2b3..7ddabec268 100644 --- a/tensorflow/contrib/tensorrt/segment/segment_test.cc +++ b/tensorflow/contrib/tensorrt/segment/segment_test.cc @@ -35,7 +35,7 @@ class SegmentTest : public ::testing::Test { TF_Operation* Add(TF_Operation* l, TF_Operation* r, TF_Graph* graph, TF_Status* s, const char* name); - std::function MakeCandidateFn( + std::function MakeCandidateFn( const std::set& node_names); protected: @@ -60,10 +60,10 @@ bool SegmentTest::GetGraphDef(TF_Graph* graph, return ret; } -std::function SegmentTest::MakeCandidateFn( +std::function SegmentTest::MakeCandidateFn( const std::set& node_names) { - return [node_names](const NodeDef& node) -> bool { - return node_names.find(node.name()) != node_names.end(); + return [node_names](const Node* node) -> bool { + return node_names.find(node->name()) != node_names.end(); }; } diff --git a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py index ff140efd48..4f6527a546 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py +++ b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py @@ -70,7 +70,7 @@ class ARModel(model.TimeSeriesModel): input_window_size: Number of past time steps of data to look at when doing the regression. output_window_size: Number of future time steps to predict. Note that - setting it to > 1 empiricaly seems to give a better fit. + setting it to > 1 empirically seems to give a better fit. num_features: number of input features per time step. num_time_buckets: Number of buckets into which to divide (time % periodicity) for generating time based features. diff --git a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py index 23452a81c3..26793c80bf 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py +++ b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py @@ -185,7 +185,7 @@ def batch_matrix_pow(matrices, powers): { matmul(A, power(matmul(A, A), (p - 1) / 2)) for odd p power(A, 0) = I - The power(A, 0) = I case is handeled by starting with accumulator set to the + The power(A, 0) = I case is handled by starting with accumulator set to the identity matrix; matrices with zero residual powers are passed through unchanged. diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py index 1afc58cfb2..6746dd7b43 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py @@ -107,7 +107,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state transition matrix. It has shape - [self.state_dimendion, self.state_dimension]. + [self.state_dimension, self.state_dimension]. """ # Pad any unused AR blocks with zeros. The extra state is necessary if # ma_order >= ar_order. @@ -127,7 +127,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state noise transform matrix. It has shape - [self.state_dimendion, self.num_features]. + [self.state_dimension, self.num_features]. """ # Noise is broadcast, through the moving average coefficients, to # un-observed parts of the latent state. diff --git a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt index 51d91399f8..e667c328ae 100644 --- a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt @@ -49,14 +49,14 @@ in the batch: If `fast` is `True`, then the solution is computed by solving the normal equations using Cholesky decomposition. Specifically, if \\(m \ge n\\) then \\(X = (A^H A + \lambda I)^{-1} A^H B\\), which solves the least-squares -problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + -\lambda ||Z||_F^2\\). If \\(m \lt n\\) then `output` is computed as +problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + \lambda ||Z||_F^2\\). +If \\(m \lt n\\) then `output` is computed as \\(X = A^H (A A^H + \lambda I)^{-1} B\\), which (for \\(\lambda = 0\\)) is the minimum-norm solution to the under-determined linear system, i.e. \\(X = \mathrm{argmin}_{Z \in \mathbb{C}^{n \times k} } ||Z||_F^2 \\), subject to \\(A Z = B\\). Notice that the fast path is only numerically stable when \\(A\\) is numerically full rank and has a condition number -\\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or\\(\lambda\\) is +\\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or \\(\lambda\\) is sufficiently large. If `fast` is `False` an algorithm based on the numerically robust complete diff --git a/tensorflow/core/common_runtime/mkl_cpu_allocator.cc b/tensorflow/core/common_runtime/mkl_cpu_allocator.cc index 43a909466e..829c19204a 100644 --- a/tensorflow/core/common_runtime/mkl_cpu_allocator.cc +++ b/tensorflow/core/common_runtime/mkl_cpu_allocator.cc @@ -19,9 +19,6 @@ limitations under the License. namespace tensorflow { -constexpr const char* MklCPUAllocator::kMaxLimitStr; -constexpr const size_t MklCPUAllocator::kDefaultMaxLimit; - } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/framework/common_shape_fns.cc b/tensorflow/core/framework/common_shape_fns.cc index 623248b6ce..2fb17c2b02 100644 --- a/tensorflow/core/framework/common_shape_fns.cc +++ b/tensorflow/core/framework/common_shape_fns.cc @@ -1210,7 +1210,7 @@ Status ConcatV2Shape(InferenceContext* c) { c->num_inputs() - 1 /* dim_index */); } -Status BroadcastBinaryOpShapeFn(InferenceContext* c) { +Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index) { ShapeHandle shape_x = c->input(0); ShapeHandle shape_y = c->input(1); if (!c->RankKnown(shape_x) || !c->RankKnown(shape_y)) { @@ -1272,7 +1272,7 @@ Status BroadcastBinaryOpShapeFn(InferenceContext* c) { } } - c->set_output(0, c->MakeShape(dims)); + c->set_output(output_index, c->MakeShape(dims)); return Status::OK(); } diff --git a/tensorflow/core/framework/common_shape_fns.h b/tensorflow/core/framework/common_shape_fns.h index 293c40e04d..789746b403 100644 --- a/tensorflow/core/framework/common_shape_fns.h +++ b/tensorflow/core/framework/common_shape_fns.h @@ -265,9 +265,15 @@ Status ConcatShape(shape_inference::InferenceContext* c, // Shape function for concat operations. Status ConcatV2Shape(shape_inference::InferenceContext* c); +// Shape function for binary operators that broadcast their inputs +// and with output to output_index. +Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index); + // Shape function for binary operators that broadcast their inputs. // Tested by ops/math_ops_test.cc. -Status BroadcastBinaryOpShapeFn(InferenceContext* c); +inline Status BroadcastBinaryOpShapeFn(InferenceContext* c) { + return BroadcastBinaryOpOutputShapeFn(c, 0); +} // Shape function for random operations. Status RandomShape(shape_inference::InferenceContext* c); diff --git a/tensorflow/core/framework/shape_inference.h b/tensorflow/core/framework/shape_inference.h index e3cc848a16..accc587000 100644 --- a/tensorflow/core/framework/shape_inference.h +++ b/tensorflow/core/framework/shape_inference.h @@ -317,6 +317,7 @@ class InferenceContext { input_tensors_as_shapes_ = input_tensors_as_shapes; } + ShapeHandle output(int64 idx) const { return outputs_[idx]; } void set_output(int idx, ShapeHandle shape) { outputs_[idx] = shape; } Status set_output(StringPiece output_name, const std::vector& shapes); diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index 333a6570dc..62aafa7930 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -933,7 +933,7 @@ class MklFusedBatchNormOp : public OpKernel { bool is_training_; T* mean_values_; T* variance_values_; - size_t depth_; // batch normalization is done for per channel. + int depth_; // batch normalization is done for per channel. void ExtractParams(OpKernelContext* context) { const Tensor& input = MklGetInput(context, 0); diff --git a/tensorflow/core/kernels/segment_reduction_ops.h b/tensorflow/core/kernels/segment_reduction_ops.h index 4abfbfb1a6..7badc00572 100644 --- a/tensorflow/core/kernels/segment_reduction_ops.h +++ b/tensorflow/core/kernels/segment_reduction_ops.h @@ -23,6 +23,13 @@ limitations under the License. // non-GPU targets. This only breaks in clang, because it's more strict for // template code and CudaAtomicMax is used in template context. +// This file requires the following include because it uses CudaAtomicMax: +// #include "tensorflow/core/util/cuda_kernel_helper.h" + +// Unfortunately we can't add the #include, since it breaks compilation for +// non-GPU targets. This only breaks in clang, because it's more strict for +// template code and CudaAtomicMax is used in template context. + #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" diff --git a/tensorflow/core/kernels/snapshot_op.cc b/tensorflow/core/kernels/snapshot_op.cc index 50157d5d48..fe04dcf72e 100644 --- a/tensorflow/core/kernels/snapshot_op.cc +++ b/tensorflow/core/kernels/snapshot_op.cc @@ -22,6 +22,26 @@ limitations under the License. namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; +typedef Eigen::GpuDevice GPUDevice; + +template +class SnapshotOp : public OpKernel { + public: + explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + const Tensor& input = context->input(0); + Tensor* output = nullptr; + // Try to use buffer forwarding to avoid an explicit copy. + OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( + {0}, 0, input.shape(), &output)); + if (!output->SharesBufferWith(input)) { + functor::Snapshot functor; + functor(context->eigen_device(), input.flat(), + output->flat()); + } + } +}; #define REGISTER_KERNEL(TYPE) \ REGISTER_KERNEL_BUILDER( \ @@ -31,6 +51,16 @@ typedef Eigen::ThreadPoolDevice CPUDevice; TF_CALL_POD_TYPES(REGISTER_KERNEL); #undef REGISTER_KERNEL +#if GOOGLE_CUDA +#define REGISTER_KERNEL(TYPE) \ + REGISTER_KERNEL_BUILDER( \ + Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ + SnapshotOp); + +TF_CALL_POD_TYPES(REGISTER_KERNEL); +#undef REGISTER_KERNEL +#endif + #if TENSORFLOW_USE_SYCL typedef Eigen::SyclDevice SyclDevice; #define REGISTER_SYCL_KERNEL(TYPE) \ diff --git a/tensorflow/core/kernels/snapshot_op.h b/tensorflow/core/kernels/snapshot_op.h index b94834f159..a18065d42b 100644 --- a/tensorflow/core/kernels/snapshot_op.h +++ b/tensorflow/core/kernels/snapshot_op.h @@ -26,29 +26,19 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" namespace tensorflow { +namespace functor { +// Functor used by SnapshotOp. template -class SnapshotOp : public OpKernel { - public: - explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} - - void Compute(OpKernelContext* context) override { - const Tensor& input = context->input(0); - Tensor* output = nullptr; - // Try to use buffer forwarding to avoid an explicit copy. - OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 0, input.shape(), &output)); - if (!output->SharesBufferWith(input)) { - // We had to allocate a new buffer since the refcount on the input was - // greater than 1. Copy the input to the new buffer. - const Device& device = context->eigen_device(); - device.memcpy(output->template flat().data(), - input.template flat().data(), - input.NumElements() * sizeof(Scalar)); - } +struct Snapshot { + void operator()(const Device& device, + typename TTypes::ConstTensor input, + typename TTypes::Tensor output) { + device.memcpy(output.data(), input.data(), input.size() * sizeof(Scalar)); } }; +} // namespace functor } // namespace tensorflow #endif // TENSORFLOW_KERNELS_SNAPSHOT_OP_H_ diff --git a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc index 52070be838..e4e3bd5220 100644 --- a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc +++ b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc @@ -24,13 +24,10 @@ limitations under the License. namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; -#define REGISTER_KERNEL(TYPE) \ - REGISTER_KERNEL_BUILDER( \ - Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ - SnapshotOp); +// Definition of the GPU implementations declared in softsign_op.cc. +#define DEFINE_GPU_KERNELS(T) template struct functor::Snapshot; -TF_CALL_POD_TYPES(REGISTER_KERNEL); -#undef REGISTER_KERNEL +TF_CALL_POD_TYPES(DEFINE_GPU_KERNELS); } // namespace tensorflow diff --git a/tensorflow/core/kernels/xent_op.cc b/tensorflow/core/kernels/xent_op.cc index a6a71fdfaf..9a3612bd72 100644 --- a/tensorflow/core/kernels/xent_op.cc +++ b/tensorflow/core/kernels/xent_op.cc @@ -17,12 +17,14 @@ limitations under the License. #define EIGEN_USE_THREADS -#include "tensorflow/core/kernels/xent_op.h" #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/kernels/xent_op.h" +#include "tensorflow/core/util/bcast.h" namespace tensorflow { @@ -41,37 +43,56 @@ class SoftmaxXentWithLogitsOp : public OpKernel { void Compute(OpKernelContext* context) override { const Tensor& logits_in = context->input(0); const Tensor& labels_in = context->input(1); - OP_REQUIRES(context, logits_in.IsSameSize(labels_in), - errors::InvalidArgument( - "logits and labels must be same size: logits_size=", - logits_in.shape().DebugString(), - " labels_size=", labels_in.shape().DebugString())); - OP_REQUIRES(context, TensorShapeUtils::IsMatrix(logits_in.shape()), - errors::InvalidArgument("logits must be 2-dimensional")); - // As we already tested that both inputs have the same shape no need to - // check that "labels" is a matrix too. + + TensorShape shape_in = logits_in.shape(); + + BCast bcast(BCast::FromShape(logits_in.shape()), + BCast::FromShape(labels_in.shape())); + if (!logits_in.IsSameSize(labels_in)) { + OP_REQUIRES(context, bcast.IsValid(), + errors::InvalidArgument( + "logits and labels must be broadcastable: logits_size=", + logits_in.shape().DebugString(), + " labels_size=", labels_in.shape().DebugString())); + shape_in = BCast::ToShape(bcast.output_shape()); + } + OP_REQUIRES(context, TensorShapeUtils::IsMatrix(shape_in), + errors::InvalidArgument("logits and labels must be beither " + "2-dimensional, or roadcasted to " + "2-dimensional")); // loss is 1-D (one per example), and size is batch_size. Tensor scratch; OP_REQUIRES_OK( context, context->allocate_temp(DataTypeToEnum::value, - TensorShape({logits_in.dim_size(0), 1}), + TensorShape({shape_in.dim_size(0), 1}), &scratch)); Tensor* loss_out = nullptr; OP_REQUIRES_OK(context, context->allocate_output( - 0, TensorShape({logits_in.dim_size(0)}), &loss_out)); + 0, TensorShape({shape_in.dim_size(0)}), &loss_out)); Tensor* back_out = nullptr; // Try to reuse the logits_in buffer for the backprop output. OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 1, logits_in.shape(), &back_out)); - if (logits_in.dim_size(0) > 0) { + {0}, 1, shape_in, &back_out)); + if (shape_in.dim_size(0) > 0) { functor::XentFunctor functor; - functor(context->eigen_device(), logits_in.matrix(), - labels_in.matrix(), scratch.matrix(), loss_out->vec(), - back_out->matrix()); + if (logits_in.IsSameSize(labels_in)) { + functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), + Eigen::array{1, 1}, + Eigen::array{1, 1}, logits_in.matrix(), + labels_in.matrix(), scratch.matrix(), loss_out->vec(), + back_out->matrix()); + } else { + functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), + BCast::ToIndexArray<2>(bcast.x_bcast()), + BCast::ToIndexArray<2>(bcast.y_bcast()), + logits_in.template shaped(bcast.x_reshape()), + labels_in.template shaped(bcast.y_reshape()), + scratch.matrix(), loss_out->vec(), back_out->matrix()); + } } } }; @@ -81,13 +102,17 @@ class SoftmaxXentWithLogitsOp : public OpKernel { namespace functor { template struct XentFunctorBase { - void operator()(const Device& d, typename TTypes::ConstMatrix logits, + void operator()(const Device& d, + const Eigen::DSizes& shape, + const Eigen::array& logits_bcast, + const Eigen::array& labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, logits, labels, scratch, loss, - backprop); + XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, + logits, labels, scratch, loss, backprop); } }; diff --git a/tensorflow/core/kernels/xent_op.h b/tensorflow/core/kernels/xent_op.h index e689fca7ff..87be17fca9 100644 --- a/tensorflow/core/kernels/xent_op.h +++ b/tensorflow/core/kernels/xent_op.h @@ -18,6 +18,7 @@ limitations under the License. // Functor definition for XentOp, must be compilable by nvcc. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + #include "tensorflow/core/framework/tensor_types.h" namespace tensorflow { @@ -33,7 +34,11 @@ struct XentFunctor { // scratch: temporary tensor, dims: batch_size, 1 // loss: output tensor for the loss, dims: batch_size. // backprop: output tensor for the backprop, dims: batch_size, num_classes. - void operator()(const Device& d, typename TTypes::ConstMatrix logits, + void operator()(const Device &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -45,7 +50,11 @@ struct XentFunctor { // specializations for both device types. template struct XentEigenImpl { - static void Compute(const Device& d, typename TTypes::ConstMatrix logits, + static void Compute(const Device &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -57,8 +66,8 @@ struct XentEigenImpl { const int kBatchDim = 0; const int kClassDim = 1; - const int batch_size = logits.dimension(kBatchDim); - const int num_classes = logits.dimension(kClassDim); + const int batch_size = shape[kBatchDim]; + const int num_classes = shape[kClassDim]; // These arrays are used to reduce along the class dimension, and broadcast // the resulting value to all classes. @@ -84,10 +93,12 @@ struct XentEigenImpl { #endif // max_logits along classes. - scratch.reshape(batch_only).device(d) = logits.maximum(along_class); + scratch.reshape(batch_only).device(d) = + logits.broadcast(logits_bcast).maximum(along_class); // logits - max_logits. - backprop.device(d) = logits - scratch.broadcast(one_by_class); + backprop.device(d) = + logits.broadcast(logits_bcast) - scratch.broadcast(one_by_class); // sum(exp(logits - max_logits)) along classes. scratch.reshape(batch_only).device(d) = backprop.exp().sum(along_class); @@ -99,15 +110,15 @@ struct XentEigenImpl { // sum(-labels * // ((logits - max_logits) - log(sum(exp(logits - max_logits))))) // along classes - loss.device(d) = - (labels * (scratch.log().eval().broadcast(one_by_class) - backprop)) - .eval() - .sum(along_class); + loss.device(d) = (labels.broadcast(labels_bcast) * + (scratch.log().eval().broadcast(one_by_class) - backprop)) + .eval() + .sum(along_class); // backprop: prob - labels, where // prob = exp(logits - max_logits) / sum(exp(logits - max_logits)) - backprop.device(d) = - (backprop.exp() / scratch.broadcast(one_by_class)) - labels; + backprop.device(d) = (backprop.exp() / scratch.broadcast(one_by_class)) - + labels.broadcast(labels_bcast); } }; diff --git a/tensorflow/core/kernels/xent_op_gpu.cu.cc b/tensorflow/core/kernels/xent_op_gpu.cu.cc index 05ee7da490..2c0c0b3a02 100644 --- a/tensorflow/core/kernels/xent_op_gpu.cu.cc +++ b/tensorflow/core/kernels/xent_op_gpu.cu.cc @@ -31,12 +31,17 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { template struct XentFunctor { - void operator()(const GPUDevice& d, typename TTypes::ConstMatrix logits, + void operator()(const GPUDevice &d, + const Eigen::DSizes &shape, + const Eigen::array &logits_bcast, + const Eigen::array &labels_bcast, + typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, logits, labels, scratch, loss, + XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, + logits, labels, scratch, loss, backprop); } }; diff --git a/tensorflow/core/ops/array_ops.cc b/tensorflow/core/ops/array_ops.cc index 88d2aa3f41..111670c361 100644 --- a/tensorflow/core/ops/array_ops.cc +++ b/tensorflow/core/ops/array_ops.cc @@ -794,11 +794,35 @@ REGISTER_OP("ReverseV2") ShapeHandle input = c->input(0); ShapeHandle axis; TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &axis)); - // TODO(aselle): if input(0)'s dimension is known we could validate axis if (c->Rank(input) > 8) { return errors::InvalidArgument( "reverse does not work on tensors with more than 8 dimensions"); } + const Tensor* axis_tensor = c->input_tensor(1); + if (axis_tensor != nullptr && c->RankKnown(input)) { + int32 rank = c->Rank(input); + std::vector axis_value; + if (axis_tensor->dtype() == DT_INT32) { + axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); + } else { + axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); + } + std::vector axes_dense(c->Rank(input), false); + for (int i = 0; i < axis_value.size(); i++) { + int64 canonical_axis = + axis_value[i] < 0 ? rank + axis_value[i] : axis_value[i]; + if (canonical_axis < 0 || canonical_axis >= rank) { + return errors::InvalidArgument("'axis'[", i, "] = ", axis_value[i], + " is out of valid range [", 0, ", ", + rank - 1); + } + if (axes_dense[canonical_axis]) { + return errors::InvalidArgument("axis ", canonical_axis, + " specified more than once."); + } + axes_dense[canonical_axis] = true; + } + } c->set_output(0, input); return Status::OK(); }); diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 1f4e9753c3..6c2fc60bab 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1062,12 +1062,27 @@ REGISTER_OP("SoftmaxCrossEntropyWithLogits") .Attr("T: {half, bfloat16, float, double}") .SetShapeFn([](InferenceContext* c) { ShapeHandle input; - TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &input)); - TF_RETURN_IF_ERROR(c->Merge(input, c->input(1), &input)); + if (c->WithRank(c->input(0), 2, &input) == Status::OK() && + c->Merge(input, c->input(1), &input) == Status::OK()) { + DimensionHandle batch_size = c->Dim(input, 0); + c->set_output(0, c->Vector(batch_size)); + c->set_output(1, input); + return Status::OK(); + } + TF_RETURN_IF_ERROR(BroadcastBinaryOpOutputShapeFn(c, 1)); - DimensionHandle batch_size = c->Dim(input, 0); + if (!c->RankKnown(c->output(1))) { + return errors::InvalidArgument( + "Shape must be broadcasted with rank 2, but is rank is unknown."); + } + + if (c->Rank(c->output(1)) != 2) { + return errors::InvalidArgument( + "Shape must be broadcasted with rank 2, but is rank ", + c->Rank(c->output(1))); + } + DimensionHandle batch_size = c->Dim(c->output(1), 0); c->set_output(0, c->Vector(batch_size)); - c->set_output(1, input); return Status::OK(); }); diff --git a/tensorflow/core/ops/nn_ops_test.cc b/tensorflow/core/ops/nn_ops_test.cc index 1b17a7cda6..289b953055 100644 --- a/tensorflow/core/ops/nn_ops_test.cc +++ b/tensorflow/core/ops/nn_ops_test.cc @@ -410,10 +410,18 @@ TEST(NNOpsTest, SoftmaxCrossEntropyWithLogits_ShapeFn) { INFER_OK(op, "[1,?];[?,2]", "[d0_0];[d0_0,d0_1|d1_1]"); INFER_OK(op, "[?,2];[1,2]", "[d1_0];in1"); - INFER_ERROR("Dimension 0 in both shapes must be equal, but are 1 and 2", op, - "[1,?];[2,?]"); - INFER_ERROR("Shape must be rank 2 but is rank 3", op, "[1,2,3];?"); - INFER_ERROR("Shapes must be equal rank, but are 2 and 3", op, "?;[1,2,3]"); + INFER_ERROR("Shape must be broadcasted with rank 2", op, "[1,2,3];?"); + INFER_ERROR("Shape must be broadcasted with rank 2", op, "?;[1,2,3]"); + + // Broadcast example + // [1,4] and [2,4] are broadcasted to [2,4] + INFER_OK(op, "[1,4];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); + // [2,4] and [2,1] are broadcasted to [2,4] + INFER_OK(op, "[2,4];[2,1]", "[d0_0];[d0_0|d1_0,d0_1]"); + // [1,?] and [2,4] are broadcasted to [2,4] + INFER_OK(op, "[1,?];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); + // [2,4] and [?,1] are broadcasted to [2,4] + INFER_OK(op, "[2,4];[?,1]", "[d0_0];[d0_0|d1_0,d0_1]"); } TEST(NNOpsTest, SparseSoftmaxCrossEntropyWithLogits_ShapeFn) { diff --git a/tensorflow/core/public/version.h b/tensorflow/core/public/version.h index 22f2c02b78..40eebd1db0 100644 --- a/tensorflow/core/public/version.h +++ b/tensorflow/core/public/version.h @@ -19,12 +19,12 @@ limitations under the License. // TensorFlow uses semantic versioning, see http://semver.org/. #define TF_MAJOR_VERSION 1 -#define TF_MINOR_VERSION 6 +#define TF_MINOR_VERSION 7 #define TF_PATCH_VERSION 0 // TF_VERSION_SUFFIX is non-empty for pre-releases (e.g. "-alpha", "-alpha.1", // "-beta", "-rc", "-rc.1") -#define TF_VERSION_SUFFIX "" +#define TF_VERSION_SUFFIX "-rc1" #define TF_STR_HELPER(x) #x #define TF_STR(x) TF_STR_HELPER(x) diff --git a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md index 956dccb64f..f3db5857ae 100644 --- a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md +++ b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md @@ -6,42 +6,42 @@ Monte Carlo integration and helpers. ## Background Monte Carlo integration refers to the practice of estimating an expectation with -a sample mean. For example, given random variable `Z in R^k` with density `p`, +a sample mean. For example, given random variable `Z in \\(R^k\\)` with density `p`, the expectation of function `f` can be approximated like: ``` -E_p[f(Z)] = \int f(z) p(z) dz - ~ S_n - := n^{-1} \sum_{i=1}^n f(z_i), z_i iid samples from p. +$$E_p[f(Z)] = \int f(z) p(z) dz$$ +$$ ~ S_n + := n^{-1} \sum_{i=1}^n f(z_i), z_i\ iid\ samples\ from\ p.$$ ``` -If `E_p[|f(Z)|] < infinity`, then `S_n --> E_p[f(Z)]` by the strong law of large -numbers. If `E_p[f(Z)^2] < infinity`, then `S_n` is asymptotically normal with -variance `Var[f(Z)] / n`. +If `\\(E_p[|f(Z)|] < infinity\\)`, then `\\(S_n\\) --> \\(E_p[f(Z)]\\)` by the strong law of large +numbers. If `\\(E_p[f(Z)^2] < infinity\\)`, then `\\(S_n\\)` is asymptotically normal with +variance `\\(Var[f(Z)] / n\\)`. Practitioners of Bayesian statistics often find themselves wanting to estimate -`E_p[f(Z)]` when the distribution `p` is known only up to a constant. For +`\\(E_p[f(Z)]\\)` when the distribution `p` is known only up to a constant. For example, the joint distribution `p(z, x)` may be known, but the evidence -`p(x) = \int p(z, x) dz` may be intractable. In that case, a parameterized -distribution family `q_lambda(z)` may be chosen, and the optimal `lambda` is the -one minimizing the KL divergence between `q_lambda(z)` and -`p(z | x)`. We only know `p(z, x)`, but that is sufficient to find `lambda`. +`\\(p(x) = \int p(z, x) dz\\)` may be intractable. In that case, a parameterized +distribution family `\\(q_\lambda(z)\\)` may be chosen, and the optimal `\\(\lambda\\)` is the +one minimizing the KL divergence between `\\(q_\lambda(z)\\)` and +`\\(p(z | x)\\)`. We only know `p(z, x)`, but that is sufficient to find `\\(\lambda\\)`. ## Log-space evaluation and subtracting the maximum Care must be taken when the random variable lives in a high dimensional space. -For example, the naive importance sample estimate `E_q[f(Z) p(Z) / q(Z)]` -involves the ratio of two terms `p(Z) / q(Z)`, each of which must have tails -dropping off faster than `O(|z|^{-(k + 1)})` in order to have finite integral. +For example, the naive importance sample estimate `\\(E_q[f(Z) p(Z) / q(Z)]\\)` +involves the ratio of two terms `\\(p(Z) / q(Z)\\)`, each of which must have tails +dropping off faster than `\\(O(|z|^{-(k + 1)})\\)` in order to have finite integral. This ratio would often be zero or infinity up to numerical precision. For that reason, we write ``` -Log E_q[ f(Z) p(Z) / q(Z) ] - = Log E_q[ exp{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C} ] + C, where -C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ]. +$$Log E_q[ f(Z) p(Z) / q(Z) ]$$ +$$ = Log E_q[ \exp\{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C\} ] + C,$$ where +$$C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ].$$ ``` The maximum value of the exponentiated term will be 0.0, and the expectation diff --git a/tensorflow/docs_src/api_guides/python/contrib.losses.md b/tensorflow/docs_src/api_guides/python/contrib.losses.md index d7f862625e..8b7442216c 100644 --- a/tensorflow/docs_src/api_guides/python/contrib.losses.md +++ b/tensorflow/docs_src/api_guides/python/contrib.losses.md @@ -107,19 +107,19 @@ weighted average over the individual prediction errors: loss = tf.contrib.losses.mean_squared_error(predictions, depths, weight) ``` -@{tf.contrib.losses.absolute_difference} -@{tf.contrib.losses.add_loss} -@{tf.contrib.losses.hinge_loss} -@{tf.contrib.losses.compute_weighted_loss} -@{tf.contrib.losses.cosine_distance} -@{tf.contrib.losses.get_losses} -@{tf.contrib.losses.get_regularization_losses} -@{tf.contrib.losses.get_total_loss} -@{tf.contrib.losses.log_loss} -@{tf.contrib.losses.mean_pairwise_squared_error} -@{tf.contrib.losses.mean_squared_error} -@{tf.contrib.losses.sigmoid_cross_entropy} -@{tf.contrib.losses.softmax_cross_entropy} -@{tf.contrib.losses.sparse_softmax_cross_entropy} +* @{tf.contrib.losses.absolute_difference} +* @{tf.contrib.losses.add_loss} +* @{tf.contrib.losses.hinge_loss} +* @{tf.contrib.losses.compute_weighted_loss} +* @{tf.contrib.losses.cosine_distance} +* @{tf.contrib.losses.get_losses} +* @{tf.contrib.losses.get_regularization_losses} +* @{tf.contrib.losses.get_total_loss} +* @{tf.contrib.losses.log_loss} +* @{tf.contrib.losses.mean_pairwise_squared_error} +* @{tf.contrib.losses.mean_squared_error} +* @{tf.contrib.losses.sigmoid_cross_entropy} +* @{tf.contrib.losses.softmax_cross_entropy} +* @{tf.contrib.losses.sparse_softmax_cross_entropy} diff --git a/tensorflow/docs_src/community/documentation.md b/tensorflow/docs_src/community/documentation.md index 003e0a25ec..6f2107ef40 100644 --- a/tensorflow/docs_src/community/documentation.md +++ b/tensorflow/docs_src/community/documentation.md @@ -477,31 +477,29 @@ should use Markdown in the docstring. Here's a simple example: -```python -def foo(x, y, name="bar"): - """Computes foo. + def foo(x, y, name="bar"): + """Computes foo. - Given two 1-D tensors `x` and `y`, this operation computes the foo. + Given two 1-D tensors `x` and `y`, this operation computes the foo. - Example: + Example: - ``` - # x is [1, 1] - # y is [2, 2] - tf.foo(x, y) ==> [3, 3] - ``` - Args: - x: A `Tensor` of type `int32`. - y: A `Tensor` of type `int32`. - name: A name for the operation (optional). + ``` + # x is [1, 1] + # y is [2, 2] + tf.foo(x, y) ==> [3, 3] + ``` + Args: + x: A `Tensor` of type `int32`. + y: A `Tensor` of type `int32`. + name: A name for the operation (optional). - Returns: - A `Tensor` of type `int32` that is the foo of `x` and `y`. + Returns: + A `Tensor` of type `int32` that is the foo of `x` and `y`. - Raises: - ValueError: If `x` or `y` are not of type `int32`. - """ -``` + Raises: + ValueError: If `x` or `y` are not of type `int32`. + """ ## Description of the docstring sections diff --git a/tensorflow/docs_src/install/install_c.md b/tensorflow/docs_src/install/install_c.md index 0481c97885..9059b3f3b6 100644 --- a/tensorflow/docs_src/install/install_c.md +++ b/tensorflow/docs_src/install/install_c.md @@ -38,7 +38,7 @@ enable TensorFlow for C: OS="linux" # Change to "darwin" for macOS TARGET_DIRECTORY="/usr/local" curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_go.md b/tensorflow/docs_src/install/install_go.md index 8f89898c92..2e47a6d212 100644 --- a/tensorflow/docs_src/install/install_go.md +++ b/tensorflow/docs_src/install/install_go.md @@ -38,7 +38,7 @@ steps to install this library and enable TensorFlow for Go: TF_TYPE="cpu" # Change to "gpu" for GPU support TARGET_DIRECTORY='/usr/local' curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.7.0-rc1.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_java.md b/tensorflow/docs_src/install/install_java.md index 0ee9c849e1..eff066d200 100644 --- a/tensorflow/docs_src/install/install_java.md +++ b/tensorflow/docs_src/install/install_java.md @@ -36,7 +36,7 @@ following to the project's `pom.xml` to use the TensorFlow Java APIs: org.tensorflow tensorflow - 1.6.0 + 1.7.0-rc1 ``` @@ -65,7 +65,7 @@ As an example, these steps will create a Maven project that uses TensorFlow: org.tensorflow tensorflow - 1.6.0 + 1.7.0-rc1 @@ -123,12 +123,12 @@ instead: org.tensorflow libtensorflow - 1.6.0 + 1.7.0-rc1 org.tensorflow libtensorflow_jni_gpu - 1.6.0 + 1.7.0-rc1 ``` @@ -147,7 +147,7 @@ refer to the simpler instructions above instead. Take the following steps to install TensorFlow for Java on Linux or macOS: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), which is the TensorFlow Java Archive (JAR). 2. Decide whether you will run TensorFlow for Java on CPU(s) only or with @@ -166,7 +166,7 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: OS=$(uname -s | tr '[:upper:]' '[:lower:]') mkdir -p ./jni curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | tar -xz -C ./jni ### Install on Windows @@ -174,10 +174,10 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: Take the following steps to install TensorFlow for Java on Windows: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), which is the TensorFlow Java Archive (JAR). 2. Download the following Java Native Interface (JNI) file appropriate for - [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.6.0.zip). + [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.7.0-rc1.zip). 3. Extract this .zip file. @@ -225,7 +225,7 @@ must be part of your `classpath`. For example, you can include the downloaded `.jar` in your `classpath` by using the `-cp` compilation flag as follows: -
javac -cp libtensorflow-1.6.0.jar HelloTF.java
+
javac -cp libtensorflow-1.7.0-rc1.jar HelloTF.java
### Running @@ -239,11 +239,11 @@ two files are available to the JVM: For example, the following command line executes the `HelloTF` program on Linux and macOS X: -
java -cp libtensorflow-1.6.0.jar:. -Djava.library.path=./jni HelloTF
+
java -cp libtensorflow-1.7.0-rc1.jar:. -Djava.library.path=./jni HelloTF
And the following command line executes the `HelloTF` program on Windows: -
java -cp libtensorflow-1.6.0.jar;. -Djava.library.path=jni HelloTF
+
java -cp libtensorflow-1.7.0-rc1.jar;. -Djava.library.path=jni HelloTF
If the program prints Hello from version, you've successfully installed TensorFlow for Java and are ready to use the API. If the program diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index 5e9a84bff6..27b696696d 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -165,7 +165,7 @@ Take the following steps to install TensorFlow with Virtualenv: Virtualenv environment:
(tensorflow)$ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl If you encounter installation problems, see [Common Installation Problems](#common_installation_problems). @@ -270,7 +270,7 @@ take the following steps:
      $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
      
If this step fails, see @@ -456,7 +456,7 @@ Take the following steps to install TensorFlow in an Anaconda environment:
      (tensorflow)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl ## Validate your installation @@ -630,14 +630,14 @@ This section documents the relevant values for Linux installations. CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp27-none-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp27-none-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -649,14 +649,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -668,14 +668,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
 
@@ -687,14 +687,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
 
diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index 55b460e189..7060ef43da 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -118,8 +118,8 @@ Take the following steps to install TensorFlow with Virtualenv: Python 2.7, the command to install TensorFlow in the active Virtualenv is as follows: -
 $ pip install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
+
 $ pip3 install --upgrade \
+     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
If you encounter installation problems, see [Common Installation Problems](#common-installation-problems). @@ -241,8 +241,8 @@ take the following steps: you are installing TensorFlow for macOS and Python 2.7 issue the following command: -
 $ sudo pip install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl 
+
 $ sudo pip3 install --upgrade \
+     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl 
If the preceding command fails, see [installation problems](#common-installation-problems). @@ -350,7 +350,7 @@ Take the following steps to install TensorFlow in an Anaconda environment: TensorFlow for Python 2.7:
 (targetDirectory)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl @@ -524,7 +524,7 @@ The value you specify depends on your Python version.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
 
@@ -532,5 +532,5 @@ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py3-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
 
diff --git a/tensorflow/docs_src/install/install_sources.md b/tensorflow/docs_src/install/install_sources.md index a7f33819b4..148f80efe2 100644 --- a/tensorflow/docs_src/install/install_sources.md +++ b/tensorflow/docs_src/install/install_sources.md @@ -350,10 +350,10 @@ Invoke `pip install` to install that pip package. The filename of the `.whl` file depends on your platform. For example, the following command will install the pip package -for TensorFlow 1.6.0 on Linux: +for TensorFlow 1.7.0rc1 on Linux:
-$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.6.0-py2-none-any.whl
+$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc1-py2-none-any.whl
 
## Validate your installation @@ -450,6 +450,8 @@ Stack Overflow and specify the `tensorflow` tag. **Linux**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc1GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.7.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
+ + @@ -469,6 +471,7 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc1GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
+ @@ -483,6 +486,8 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
+ + diff --git a/tensorflow/docs_src/mobile/optimizing.md b/tensorflow/docs_src/mobile/optimizing.md index ca9cb043e9..778e4d3a62 100644 --- a/tensorflow/docs_src/mobile/optimizing.md +++ b/tensorflow/docs_src/mobile/optimizing.md @@ -233,6 +233,8 @@ order by how long they took. From left to right, the columns are: - The cumulative total time of this and the previous ops in the table. This is handy for understanding what the distribution of work is across the layers, to see if just a few of the nodes are taking up most of the time. + +- The amount of memory consumed by outputs of this type of op. - Name of the node. diff --git a/tensorflow/docs_src/mobile/prepare_models.md b/tensorflow/docs_src/mobile/prepare_models.md index 360ee302aa..8b22c04d87 100644 --- a/tensorflow/docs_src/mobile/prepare_models.md +++ b/tensorflow/docs_src/mobile/prepare_models.md @@ -60,7 +60,7 @@ and serialized as protocol buffers: the `NodeDef`, so if all the `Variable` weights are converted to `Const` nodes, then we only need a single `GraphDef` file to hold the model architecture and the weights. Freezing the graph handles the process of loading the - checkpoints, and then converts all Consts to Variables. You can then load the + checkpoints, and then converts all Variables to Consts. You can then load the resulting file in a single call, without having to restore variable values from checkpoints. One thing to watch out for with `GraphDef` files is that sometimes they’re stored in text format for easy inspection. These versions diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 4f61c01f65..a0dd409205 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -1065,7 +1065,7 @@ py_test( py_test( name = "framework_importer_test", - size = "medium", + size = "large", srcs = ["framework/importer_test.py"], main = "framework/importer_test.py", srcs_version = "PY2AND3", diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index d0ba8020c1..64c1760d5e 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -315,21 +315,39 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf_4, np.asarray(x_np)[:, ::-1]) self.assertAllEqual(x_tf_5, np.asarray(x_np)[::-1, ::-1]) + # This test covers the axis validation in the shape function + # (no eval()) + def testInvalidAxis(self): + x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [-30]) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [2]) + with self.assertRaisesRegexp(ValueError, + "axis 0 specified more than once"): + array_ops.reverse_v2(x_np, [0, -2]) + # This is the version of reverse that uses axis indices rather than # bool tensors # TODO(b/32254538): Change this test to use array_ops.reverse + # + # Note: this test passes placeholder as constant axis is validated + # in shape function (see testInvalidAxis) def testInvalid(self): x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + axis = array_ops.placeholder(dtypes.int32) with self.test_session(): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [-30]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [-30]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [2]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "axis 0 specified more than once"): - array_ops.reverse_v2(x_np, [0, -2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [0, -2]}) def testReverse1DimAuto(self): for dtype in [ @@ -890,7 +908,7 @@ class StridedSliceAssignChecker(object): var = resource_variable_ops.ResourceVariable(self.x) else: var = variables.Variable(self.x) - sess.run(variables.initialize_variables([var])) + sess.run(variables.variables_initializer([var])) val = sess.run(var[index].assign(value)) # val_copy is used to check that tf.assign works equivalently to the # assign method above. diff --git a/tensorflow/python/kernel_tests/testdata/BUILD b/tensorflow/python/kernel_tests/testdata/BUILD index a4a0dfc139..45264c773a 100644 --- a/tensorflow/python/kernel_tests/testdata/BUILD +++ b/tensorflow/python/kernel_tests/testdata/BUILD @@ -1,7 +1,7 @@ # Data files for kernel tests. package( - default_visibility = ["//tensorflow:internal"], + default_visibility = ["//visibility:public"], ) licenses(["notice"]) # Apache 2.0 diff --git a/tensorflow/python/kernel_tests/xent_op_test.py b/tensorflow/python/kernel_tests/xent_op_test.py index e3e120a4eb..60c726d54c 100644 --- a/tensorflow/python/kernel_tests/xent_op_test.py +++ b/tensorflow/python/kernel_tests/xent_op_test.py @@ -18,10 +18,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import itertools +import sys + import numpy as np +from tensorflow.python.client import session from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl @@ -88,7 +94,7 @@ class XentTest(test.TestCase): 4.]]]).astype(dtype) np_labels = np.array([[[0., 0., 0., 1.]], [[0., .5, .5, 0.]]]).astype(dtype) - self.assertRaisesRegexp(ValueError, "must be rank 2", + self.assertRaisesRegexp(ValueError, "rank 2, but is rank 3", gen_nn_ops.softmax_cross_entropy_with_logits, np_features, np_labels) @@ -128,6 +134,24 @@ class XentTest(test.TestCase): self.assertAllClose( np.array([1.3862, 1.9401]), np_loss, rtol=1.e-3, atol=1.e-3) + def testShapeBroadcast(self): + np_f = np.array([[1., 2., 3., 4.], + [1., 2., 3., 4.]]).astype(np.float32) + np_l = np.array([[0., 0., 0., 1.], + [0., .5, .5, 0.]]).astype(np.float32) + np_loss, np_backprop = self._npXent(np_f, np_l) + tf_f = constant_op.constant( + np.array([[1., 2., 3., 4.]]).astype(np.float32)) + tf_l = constant_op.constant( + np.array([[0., 0., 0., 1.], [0., .5, .5, 0.]]).astype(np.float32)) + for use_gpu in [False, True]: + with self.test_session(use_gpu=use_gpu) as sess: + loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( + tf_f, tf_l) + tf_loss, tf_backprop = sess.run([loss, backprop]) + self.assertAllCloseAccordingToType(np_loss, tf_loss) + self.assertAllCloseAccordingToType(np_backprop, tf_backprop) + def testShapeMismatch(self): with self.test_session(): with self.assertRaises(ValueError): @@ -260,5 +284,60 @@ class XentTest(test.TestCase): self.assertAllEqual(np_loss, tf_loss) +class XentBenchmark(test.Benchmark): + + def benchmarkZeroDimension(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "zero_dimension_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + logits = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + def benchmarkSingleClass(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "single_class_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = constant_op.constant([[1.], [-1.], [0.]], + dtype=dtypes.float32) + logits = constant_op.constant([[-1.], [0.], [1.]], + dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/layers/convolutional.py b/tensorflow/python/layers/convolutional.py index 74e7c63fb3..2d99b1688f 100644 --- a/tensorflow/python/layers/convolutional.py +++ b/tensorflow/python/layers/convolutional.py @@ -180,6 +180,8 @@ class _Conv(base.Layer): # bias_add when computing gradients. To use bias_add, we collapse Z # and Y into a single dimension to obtain a 4D input tensor. outputs_shape = outputs.shape.as_list() + if outputs_shape[0] is None: + outputs_shape[0] = -1 outputs_4d = array_ops.reshape(outputs, [outputs_shape[0], outputs_shape[1], outputs_shape[2] * outputs_shape[3], diff --git a/tensorflow/python/layers/convolutional_test.py b/tensorflow/python/layers/convolutional_test.py index 160e732b67..cdb42f5bd1 100644 --- a/tensorflow/python/layers/convolutional_test.py +++ b/tensorflow/python/layers/convolutional_test.py @@ -325,6 +325,12 @@ class ConvTest(test.TestCase): self.assertEqual(conv3d.kernel_constraint, k_constraint) self.assertEqual(conv3d.bias_constraint, b_constraint) + def testConv3DChannelsFirst(self): + # Test case for GitHub issue 15655 + images = array_ops.placeholder( + dtype=dtypes.float32, shape=[None, 1, 32, 32, 32]) + conv_layers.conv3d(images, 32, 9, data_format='channels_first') + @test_util.with_c_api class SeparableConv1DTest(test.TestCase): diff --git a/tensorflow/python/ops/linalg_ops.py b/tensorflow/python/ops/linalg_ops.py index 5b4fb4f7c8..170861b43f 100644 --- a/tensorflow/python/ops/linalg_ops.py +++ b/tensorflow/python/ops/linalg_ops.py @@ -429,7 +429,7 @@ def svd(tensor, full_matrices=False, compute_uv=True, name=None): u, s, v_adj = np.linalg.svd(a, full_matrices=False) np_a_approx = np.dot(u, np.dot(np.diag(s), v_adj)) # tf_a_approx and np_a_approx should be numerically close. - ```` + ``` @end_compatibility """ s, u, v = gen_linalg_ops.svd( diff --git a/tensorflow/python/training/monitored_session.py b/tensorflow/python/training/monitored_session.py index 6c5c9e01a7..4ce6f6d002 100644 --- a/tensorflow/python/training/monitored_session.py +++ b/tensorflow/python/training/monitored_session.py @@ -281,13 +281,14 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name scaffold=None, hooks=None, chief_only_hooks=None, - save_checkpoint_secs=600, + save_checkpoint_secs=USE_DEFAULT, save_summaries_steps=USE_DEFAULT, save_summaries_secs=USE_DEFAULT, config=None, stop_grace_period_secs=120, log_step_count_steps=100, - max_wait_secs=7200): + max_wait_secs=7200, + save_checkpoint_steps=USE_DEFAULT): """Creates a `MonitoredSession` for training. For a chief, this utility sets proper session initializer/restorer. It also @@ -310,8 +311,10 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name chief_only_hooks: list of `SessionRunHook` objects. Activate these hooks if `is_chief==True`, ignore otherwise. save_checkpoint_secs: The frequency, in seconds, that a checkpoint is saved - using a default checkpoint saver. If `save_checkpoint_secs` is set to - `None`, then the default checkpoint saver isn't used. + using a default checkpoint saver. If both `save_checkpoint_steps` and + `save_checkpoint_secs` are set to `None`, then the default checkpoint + saver isn't used. If both are provided, then only `save_checkpoint_secs` + is used. Default 600. save_summaries_steps: The frequency, in number of global steps, that the summaries are written to disk using a default summary saver. If both `save_summaries_steps` and `save_summaries_secs` are set to `None`, then @@ -330,6 +333,11 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name become available. This should be kept relatively short to help detect incorrect code, but sometimes may need to be increased if the chief takes a while to start up. + save_checkpoint_steps: The frequency, in number of global steps, that a + checkpoint is saved using a default checkpoint saver. If both + `save_checkpoint_steps` and `save_checkpoint_secs` are set to `None`, then + the default checkpoint saver isn't used. If both are provided, then only + `save_checkpoint_secs` is used. Default not enabled. Returns: A `MonitoredSession` object. @@ -342,6 +350,15 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name elif save_summaries_steps == USE_DEFAULT: save_summaries_steps = None + if (save_checkpoint_steps == USE_DEFAULT and + save_checkpoint_secs == USE_DEFAULT): + save_checkpoint_steps = None + save_checkpoint_secs = 600 + elif save_checkpoint_secs == USE_DEFAULT: + save_checkpoint_secs = None + elif save_checkpoint_steps == USE_DEFAULT: + save_checkpoint_steps = None + scaffold = scaffold or Scaffold() if not is_chief: session_creator = WorkerSessionCreator( @@ -374,9 +391,13 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name save_steps=save_summaries_steps, save_secs=save_summaries_secs, output_dir=checkpoint_dir)) - if save_checkpoint_secs and save_checkpoint_secs > 0: + if (save_checkpoint_secs and save_checkpoint_secs > 0) or ( + save_checkpoint_steps and save_checkpoint_steps > 0): all_hooks.append(basic_session_run_hooks.CheckpointSaverHook( - checkpoint_dir, save_secs=save_checkpoint_secs, scaffold=scaffold)) + checkpoint_dir, + save_steps=save_checkpoint_steps, + save_secs=save_checkpoint_secs, + scaffold=scaffold)) if hooks: all_hooks.extend(hooks) diff --git a/tensorflow/python/training/monitored_session_test.py b/tensorflow/python/training/monitored_session_test.py index 159b2d5c16..3806056f01 100644 --- a/tensorflow/python/training/monitored_session_test.py +++ b/tensorflow/python/training/monitored_session_test.py @@ -282,6 +282,42 @@ class MonitoredTrainingSessionTest(test.TestCase): is_chief=True, checkpoint_dir=logdir) as session: self.assertEqual(2, session.run(gstep)) + def test_save_checkpoint_steps(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_steps') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_steps=100, + log_step_count_steps=10) as session: + for _ in range(100): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(100, session.run(gstep)) + + def test_save_checkpoint_secs(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_secs') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_secs=0.1, + log_step_count_steps=10) as session: + session.run(new_gstep) + time.sleep(0.2) + for _ in range(10): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(11, session.run(gstep)) + def test_summaries_steps(self): logdir = _test_dir(self.get_temp_dir(), 'test_summaries_steps') with ops.Graph().as_default(): diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 2d3cb415fe..fcc57d506e 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -22,6 +22,7 @@ load( load( "//third_party/mkl:build_defs.bzl", "if_mkl", + "if_mkl_lnx_x64" ) def register_extension_info(**kwargs): @@ -202,7 +203,8 @@ def tf_copts(android_optimization_level_override="-O2", is_external=False): "-ftemplate-depth=900"]) + if_cuda(["-DGOOGLE_CUDA=1"]) + if_tensorrt(["-DGOOGLE_TENSORRT=1"]) - + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML", "-fopenmp",]) + + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML"]) + + if_mkl_lnx_x64(["-fopenmp"]) + if_android_arm(["-mfpu=neon"]) + if_linux_x86_64(["-msse3"]) + if_ios_x86_64(["-msse4.1"]) diff --git a/tensorflow/tools/api/golden/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/tensorflow.train.pbtxt index c75ee474aa..bec72e1e60 100644 --- a/tensorflow/tools/api/golden/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.train.pbtxt @@ -238,7 +238,7 @@ tf_module { } member_method { name: "MonitoredTrainingSession" - argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'600\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\'], " + argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\', \'save_checkpoint_steps\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\', \'\'], " } member_method { name: "NewCheckpointReader" diff --git a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh index e1b56b9a25..7d471b4703 100755 --- a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh +++ b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh @@ -31,5 +31,5 @@ export TF_NEED_OPENCL_SYCL=0 export TF_NEED_MKL=0 export COMPUTECPP_PATH="/usr/local" -export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" build_libtensorflow_tarball "-cpu-darwin-$(uname -m)" diff --git a/tensorflow/tools/docker/Dockerfile.devel b/tensorflow/tools/docker/Dockerfile.devel index 22c73c3fe1..11f476d12c 100644 --- a/tensorflow/tools/docker/Dockerfile.devel +++ b/tensorflow/tools/docker/Dockerfile.devel @@ -70,7 +70,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # TODO(craigcitro): Don't install the pip package, since it makes it # more difficult to experiment with local changes. Instead, just add diff --git a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl index 3690e7dfe5..037d13116e 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl +++ b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl @@ -3,7 +3,7 @@ FROM tensorflow/tensorflow:latest-devel LABEL maintainer="Clayne Robison" # These arguments are parameterized. Use --build-args to override. -ARG TF_BRANCH=r1.6 +ARG TF_BRANCH=r1.7 ARG WHL_DIR=/whl RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/tensorflow/tools/docker/Dockerfile.devel-gpu b/tensorflow/tools/docker/Dockerfile.devel-gpu index 69ba340f92..1fcb6428b2 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-gpu +++ b/tensorflow/tools/docker/Dockerfile.devel-gpu @@ -79,7 +79,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 3fbdb5cacd..0ede8c6370 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -138,7 +138,6 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]), outs = ["include/tensorflow/c/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", @@ -176,7 +175,6 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]), outs = ["include/tensorflow/jni/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index dd75eda231..62fec2c402 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -127,7 +127,6 @@ filegroup( "@org_python_pypi_backports_weakref//:LICENSE", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]) + if_not_windows([ "@nccl_archive//:LICENSE.txt", ]) + tf_additional_license_deps(), diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index e0152da4df..365e8d6b08 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.6.0' +_VERSION = '1.7.0-rc1' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', @@ -39,7 +39,7 @@ REQUIRED_PACKAGES = [ 'numpy >= 1.13.3', 'six >= 1.10.0', 'protobuf >= 3.4.0', - 'tensorboard >= 1.6.0, < 1.7.0', + 'tensorboard >= 1.7.0, < 1.8.0', 'termcolor >= 1.1.0', ] @@ -62,7 +62,7 @@ else: if 'tf_nightly' in project_name: for i, pkg in enumerate(REQUIRED_PACKAGES): if 'tensorboard' in pkg: - REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.7.0a0, < 1.8.0a0' + REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.8.0a0, < 1.9.0a0' break # weakref.finalize and enum were introduced in Python 3.4 diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 9fcbfb664b..5f6e717532 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -15,6 +15,11 @@ load("@io_bazel_rules_closure//closure/private:java_import_external.bzl", "java_ load("@io_bazel_rules_closure//closure:defs.bzl", "filegroup_external") +# Sanitize a dependency so that it works correctly from code that includes +# TensorFlow as a submodule. +def clean_dep(dep): + return str(Label(dep)) + # If TensorFlow is linked as a submodule. # path_prefix is no longer used. # tf_repo_name is thought to be under consideration. @@ -32,17 +37,37 @@ def tf_workspace(path_prefix="", tf_repo_name=""): arm_compiler_configure( name="local_config_arm_compiler", remote_config_repo="../arm_compiler", - build_file = str(Label("//third_party/toolchains/cpus/arm:BUILD"))) + build_file = clean_dep("//third_party/toolchains/cpus/arm:BUILD")) mkl_repository( - name = "mkl", + name = "mkl_linux", + urls = [ + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", + ], + sha256 = "feacc3d82565c1231470359b42c696236fae873704e0b013436afba5fd4fd30f", + strip_prefix = "mklml_lnx_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") + ) + mkl_repository( + name = "mkl_windows", + urls = [ + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip" + ], + sha256 = "24bae8d7b22b431a654acadea43f2243c46ae6b1e5a73a4a936825f31d284ee4", + strip_prefix = "mklml_win_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") + ) + mkl_repository( + name = "mkl_darwin", urls = [ - "https://mirror.bazel.build/github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", - "https://github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz" ], - sha256 = "6b07cb7e5451db67c2e31e785ae458b18f7f363c60a61685488f69e9ae7199d4", - strip_prefix = "mklml_lnx_2018.0.1.20171007", - build_file = str(Label("//third_party/mkl:mkl.BUILD")), + sha256 = "0e954ec6fd3dc5e37f64c4043f6b5613dd687558da3df1028b3b7c29ff5cf77f", + strip_prefix = "mklml_mac_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") ) if path_prefix: @@ -52,12 +77,12 @@ def tf_workspace(path_prefix="", tf_repo_name=""): tf_http_archive( name = "mkl_dnn", urls = [ - "https://mirror.bazel.build/github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", - "https://github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", + "https://mirror.bazel.build/github.com/intel/mkl-dnn/archive/v0.12.tar.gz", + "https://github.com/intel/mkl-dnn/archive/v0.12.tar.gz", ], - sha256 = "02e244f63dd95402691a361392504c143eede9a89043426f174836638a9cbf09", - strip_prefix = "mkl-dnn-e0bfcaa7fcb2b1e1558f5f0676933c1db807a729", - build_file = str(Label("//third_party/mkl_dnn:mkldnn.BUILD")), + sha256 = "86fa2a8c12a56e3b725945acedeaa82492746be02545aba6d710f097e013e19e", + strip_prefix = "mkl-dnn-0.12", + build_file = clean_dep("//third_party/mkl_dnn:mkldnn.BUILD"), ) tf_http_archive( @@ -68,7 +93,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "5996380e3e8b981f55d1c8d58e709c00dbb4806ba367be75d0925a68cc2f6478", strip_prefix = "abseil-cpp-720c017e30339fd1786ce4aac68bc8559736e53f", - build_file = str(Label("//third_party:com_google_absl.BUILD")), + build_file = clean_dep("//third_party:com_google_absl.BUILD"), ) tf_http_archive( @@ -79,8 +104,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "0cadb31a35b514bf2dfd6b5d38205da94ef326ec6908fc3fd7c269948467214f", strip_prefix = "eigen-eigen-2355b229ea4c", - build_file = str(Label("//third_party:eigen.BUILD")), - patch_file = str(Label("//third_party:eigen_fix_cuda_compilation.patch")) + build_file = clean_dep("//third_party:eigen.BUILD"), + patch_file = clean_dep("//third_party:eigen_fix_cuda_compilation.patch") ) tf_http_archive( @@ -93,7 +118,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # remove the whitelist entry in third_party/repo.bzl. # "https://github.com/raspberrypi/tools/archive/0e906ebc527eab1cdbf7adabff5b474da9562e9f.tar.gz", ], - build_file = str(Label("//:arm_compiler.BUILD")), + build_file = clean_dep("//:arm_compiler.BUILD"), ) tf_http_archive( @@ -104,7 +129,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ade869c3f42f23b5263c7d594aa3c7e5e61ac6a3afcaf5d6e42899d2a7986ce", strip_prefix = "libxsmm-1.8.1", - build_file = str(Label("//third_party:libxsmm.BUILD")), + build_file = clean_dep("//third_party:libxsmm.BUILD"), ) tf_http_archive( @@ -117,7 +142,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "932075525642b04ac6f1b50589f1df5cd72ec2f448b721fd32234cf183f0e755", strip_prefix = "or-tools-253f7955c6a1fd805408fba2e42ac6d45b312d15/src", - build_file = str(Label("//third_party:ortools.BUILD")), + build_file = clean_dep("//third_party:ortools.BUILD"), ) tf_http_archive( @@ -149,7 +174,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6560547c63e4af82b0f202cb710ceabb3f21347a4b996db565a411da5b17aba0", strip_prefix = "farmhash-816a4ae622e964763ca0862d9dbd19324a1eaf45", - build_file = str(Label("//third_party:farmhash.BUILD")), + build_file = clean_dep("//third_party:farmhash.BUILD"), ) tf_http_archive( @@ -160,7 +185,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "0f30a15b1566d93f146c8d149878a06e91d9bb7ec2cfd76906df62a82be4aac9", strip_prefix = "highwayhash-dfcb97ca4fe9277bf9dc1802dd979b071896453b", - build_file = str(Label("//third_party:highwayhash.BUILD")), + build_file = clean_dep("//third_party:highwayhash.BUILD"), ) tf_http_archive( @@ -171,7 +196,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "00b0891c678c065446ca59bcee64719d0096d54d6886e6e472aeee2e170ae324", strip_prefix = "nasm-2.12.02", - build_file = str(Label("//third_party:nasm.BUILD")), + build_file = clean_dep("//third_party:nasm.BUILD"), ) tf_http_archive( @@ -182,7 +207,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "c15a9607892113946379ccea3ca8b85018301b200754f209453ab21674268e77", strip_prefix = "libjpeg-turbo-1.5.1", - build_file = str(Label("//third_party/jpeg:jpeg.BUILD")), + build_file = clean_dep("//third_party/jpeg:jpeg.BUILD"), ) tf_http_archive( @@ -193,7 +218,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "716c59c7dfc808a4c368f8ada526932be72b2fcea11dd85dc9d88b1df1dfe9c2", strip_prefix = "libpng-1.2.53", - build_file = str(Label("//third_party:png.BUILD")), + build_file = clean_dep("//third_party:png.BUILD"), ) tf_http_archive( @@ -204,7 +229,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "208780b3616f9de0aeb50822b7a8f5482f6515193859e91ed61637be6ad74fd4", strip_prefix = "sqlite-amalgamation-3200000", - build_file = str(Label("//third_party:sqlite.BUILD")), + build_file = clean_dep("//third_party:sqlite.BUILD"), ) tf_http_archive( @@ -215,7 +240,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "34a7377ba834397db019e8eb122e551a49c98f49df75ec3fcc92b9a794a4f6d1", strip_prefix = "giflib-5.1.4", - build_file = str(Label("//third_party:gif.BUILD")), + build_file = clean_dep("//third_party:gif.BUILD"), ) tf_http_archive( @@ -226,7 +251,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a", strip_prefix = "six-1.10.0", - build_file = str(Label("//third_party:six.BUILD")), + build_file = clean_dep("//third_party:six.BUILD"), ) tf_http_archive( @@ -237,7 +262,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "ff6d2e2962d834acb125cc4dcc80c54a8c17c253f4cc9d9c43b5102a560bb75d", strip_prefix = "astor-0.6.2", - build_file = str(Label("//third_party:astor.BUILD")), + build_file = clean_dep("//third_party:astor.BUILD"), ) tf_http_archive( @@ -248,7 +273,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "7068908321ecd2774f145193c4b34a11305bd104b4551b09273dfd1d6a374930", strip_prefix = "gast-0.2.0", - build_file = str(Label("//third_party:gast.BUILD")), + build_file = clean_dep("//third_party:gast.BUILD"), ) tf_http_archive( @@ -259,7 +284,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b", strip_prefix = "termcolor-1.1.0", - build_file = str(Label("//third_party:termcolor.BUILD")), + build_file = clean_dep("//third_party:termcolor.BUILD"), ) tf_http_archive( @@ -280,7 +305,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "8813bf712a66b3d8b85dc289e1104ed220f1878cf981e2fe756dfaabe9a82892", strip_prefix = "backports.weakref-1.0rc1/src", - build_file = str(Label("//third_party:backports_weakref.BUILD")), + build_file = clean_dep("//third_party:backports_weakref.BUILD"), ) tf_http_archive( @@ -291,7 +316,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2dadd04a2802de27e0fe5a19b76538f6da9d39ff244036afa00c1bba754de5ee", strip_prefix = "codegen-1.0", - build_file = str(Label("//third_party:codegen.BUILD")), + build_file = clean_dep("//third_party:codegen.BUILD"), ) filegroup_external( @@ -376,7 +401,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://ftp.exim.org/pub/pcre/pcre-8.39.tar.gz", ], strip_prefix = "pcre-8.39", - build_file = str(Label("//third_party:pcre.BUILD")), + build_file = clean_dep("//third_party:pcre.BUILD"), ) tf_http_archive( @@ -388,7 +413,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://pilotfiber.dl.sourceforge.net/project/swig/swig/swig-3.0.8/swig-3.0.8.tar.gz", ], strip_prefix = "swig-3.0.8", - build_file = str(Label("//third_party:swig.BUILD")), + build_file = clean_dep("//third_party:swig.BUILD"), ) tf_http_archive( @@ -399,7 +424,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://curl.haxx.se/download/curl-7.49.1.tar.gz", ], strip_prefix = "curl-7.49.1", - build_file = str(Label("//third_party:curl.BUILD")), + build_file = clean_dep("//third_party:curl.BUILD"), ) tf_http_archive( @@ -421,7 +446,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/antirez/linenoise/archive/c894b9e59f02203dbe4e2be657572cf88c4230c3.tar.gz", ], strip_prefix = "linenoise-c894b9e59f02203dbe4e2be657572cf88c4230c3", - build_file = str(Label("//third_party:linenoise.BUILD")), + build_file = clean_dep("//third_party:linenoise.BUILD"), ) # TODO(phawkins): currently, this rule uses an unofficial LLVM mirror. @@ -434,7 +459,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1efbb9b05af88368be984d2f6526061d4a857181ef10f8841889a3a46869bb01", strip_prefix = "llvm-1c3cdea2f181d8e14ee184466c5fb237f1b4cda8", - build_file = str(Label("//third_party/llvm:llvm.BUILD")), + build_file = clean_dep("//third_party/llvm:llvm.BUILD"), ) tf_http_archive( @@ -445,7 +470,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "108532fb94c6f227558d45be3f3347b52539f0f58290a7bb31ec06c462d05326", strip_prefix = "lmdb-LMDB_0.9.19/libraries/liblmdb", - build_file = str(Label("//third_party:lmdb.BUILD")), + build_file = clean_dep("//third_party:lmdb.BUILD"), ) tf_http_archive( @@ -456,7 +481,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "07d34db40593d257324ec5fb9debc4dc33f29f8fb44e33a2eeb35503e61d0fe2", strip_prefix = "jsoncpp-11086dd6a7eba04289944367ca82cea71299ed70", - build_file = str(Label("//third_party:jsoncpp.BUILD")), + build_file = clean_dep("//third_party:jsoncpp.BUILD"), ) tf_http_archive( @@ -477,7 +502,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "36658cb768a54c1d4dec43c3116c27ed893e88b02ecfcb44f2166f9c0b7f2a0d", strip_prefix = "zlib-1.2.8", - build_file = str(Label("//third_party:zlib.BUILD")), + build_file = clean_dep("//third_party:zlib.BUILD"), ) tf_http_archive( @@ -487,7 +512,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://www.kurims.kyoto-u.ac.jp/~ooura/fft.tgz", ], sha256 = "52bb637c70b971958ec79c9c8752b1df5ff0218a4db4510e60826e0cb79b5296", - build_file = str(Label("//third_party/fft2d:fft2d.BUILD")), + build_file = clean_dep("//third_party/fft2d:fft2d.BUILD"), ) tf_http_archive( @@ -498,7 +523,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2f7504c73d85bac842e893340333be8cb8561710642fc9562fccdd9d2c3fcc94", strip_prefix = "snappy-1.1.4", - build_file = str(Label("//third_party:snappy.BUILD")), + build_file = clean_dep("//third_party:snappy.BUILD"), ) tf_http_archive( @@ -509,7 +534,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ca86fb6179ecbff789cc67c836139c1bbc0324ed8c04643405a30bf26325176", strip_prefix = "nccl-03d856977ecbaac87e598c0c4bafca96761b9ac7", - build_file = str(Label("//third_party:nccl.BUILD")), + build_file = clean_dep("//third_party:nccl.BUILD"), ) tf_http_archive( @@ -520,8 +545,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "dd035d57c8f19b0b612dd6eefe6e5eebad76f506e302cccb7c2066f25a83585e", strip_prefix = "librdkafka-0.11.1", - build_file = str(Label("//third_party:kafka/BUILD")), - patch_file = str(Label("//third_party/kafka:config.patch")), + build_file = clean_dep("//third_party:kafka/BUILD"), + patch_file = clean_dep("//third_party/kafka:config.patch"), ) tf_http_archive( @@ -532,7 +557,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "b888d8ce5fc10254c3dd6c9020c7764dd53cf39cf011249d0b4deda895de1b7c", strip_prefix = "aws-sdk-cpp-1.3.15", - build_file = str(Label("//third_party:aws.BUILD")), + build_file = clean_dep("//third_party:aws.BUILD"), ) java_import_external( @@ -568,7 +593,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "3c8f25c02e806c3ce0ab5fb7da1817f89fc9732709024e2a81b6b82f7cc792a8", strip_prefix = "jemalloc-4.4.0", - build_file = str(Label("//third_party:jemalloc.BUILD")), + build_file = clean_dep("//third_party:jemalloc.BUILD"), ) java_import_external( @@ -613,7 +638,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "e0928ca4aa10ea1e0551e2d7ce4d1d7ea2d84b2abbdef082b0da84268791d0c4", strip_prefix = "pprof-c0fb62ec88c411cc91194465e54db2632845b650", - build_file = str(Label("//third_party:pprof.BUILD")), + build_file = clean_dep("//third_party:pprof.BUILD"), ) tf_http_archive( @@ -624,7 +649,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6bfa06ab52a650ae7ee6963143a0bbc667d6504822cbd9670369b598f18c58c3", strip_prefix = "cub-1.8.0", - build_file = str(Label("//third_party:cub.BUILD")), + build_file = clean_dep("//third_party:cub.BUILD"), ) tf_http_archive( @@ -635,7 +660,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/cython/cython/archive/3732784c45cfb040a5b0936951d196f83a12ea17.tar.gz", ], strip_prefix = "cython-3732784c45cfb040a5b0936951d196f83a12ea17", - build_file = str(Label("//third_party:cython.BUILD")), + build_file = clean_dep("//third_party:cython.BUILD"), delete = ["BUILD.bazel"], ) @@ -657,7 +682,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", "https://github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", ], - build_file = str(Label("//third_party:arm_neon_2_x86_sse.BUILD")), + build_file = clean_dep("//third_party:arm_neon_2_x86_sse.BUILD"), ) tf_http_archive( @@ -668,7 +693,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", "https://github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", ], - build_file = str(Label("//third_party/flatbuffers:flatbuffers.BUILD")), + build_file = clean_dep("//third_party/flatbuffers:flatbuffers.BUILD"), ) tf_http_archive( @@ -678,7 +703,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", ], - build_file = str(Label("//third_party:tflite_mobilenet.BUILD")), + build_file = clean_dep("//third_party:tflite_mobilenet.BUILD"), ) tf_http_archive( @@ -688,7 +713,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip" ], - build_file = str(Label("//third_party:tflite_smartreply.BUILD")), + build_file = clean_dep("//third_party:tflite_smartreply.BUILD"), ) ############################################################################## @@ -752,7 +777,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # Needed by Protobuf native.bind( name = "python_headers", - actual = str(Label("//util/python:python_headers")), + actual = clean_dep("//util/python:python_headers"), ) # Needed by Protobuf diff --git a/third_party/mkl/BUILD b/third_party/mkl/BUILD index b27d341404..c2adf578c7 100644 --- a/third_party/mkl/BUILD +++ b/third_party/mkl/BUILD @@ -1,7 +1,5 @@ licenses(["notice"]) # 3-Clause BSD -exports_files(["LICENSE"]) - config_setting( name = "using_mkl", values = { @@ -10,17 +8,51 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "using_mkl_lnx_x64", + values = { + "cpu": "k8", + "define": "using_mkl=true", + }, + visibility = ["//visibility:public"], +) + load( "//third_party/mkl:build_defs.bzl", "if_mkl", ) +filegroup( + name = "LICENSE", + srcs = ["MKL_LICENSE"] + select({ + "@org_tensorflow//tensorflow:linux_x86_64": [ + "@mkl_linux//:LICENSE", + ], + "@org_tensorflow//tensorflow:darwin": [ + "@mkl_darwin//:LICENSE", + ], + "@org_tensorflow//tensorflow:windows": [ + "@mkl_windows//:LICENSE", + ], + }), + visibility = ["//visibility:public"], +) + cc_library( name = "intel_binary_blob", - srcs = if_mkl([ - "@mkl//:libmklml_intel.so", - "@mkl//:libiomp5.so", - ]), visibility = ["//visibility:public"], - deps = ["@mkl//:mkl_headers"], + deps = select({ + "@org_tensorflow//tensorflow:linux_x86_64": [ + "@mkl_linux//:mkl_headers", + "@mkl_linux//:mkl_libs_linux", + ], + "@org_tensorflow//tensorflow:darwin": [ + "@mkl_darwin//:mkl_headers", + "@mkl_darwin//:mkl_libs_darwin", + ], + "@org_tensorflow//tensorflow:windows": [ + "@mkl_windows//:mkl_headers", + "@mkl_windows//:mkl_libs_windows", + ], + }), ) diff --git a/third_party/mkl/MKL_LICENSE b/third_party/mkl/MKL_LICENSE new file mode 100644 index 0000000000..9c8f3ea087 --- /dev/null +++ b/third_party/mkl/MKL_LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/third_party/mkl/build_defs.bzl b/third_party/mkl/build_defs.bzl index 8b73ddabdd..53e02769da 100644 --- a/third_party/mkl/build_defs.bzl +++ b/third_party/mkl/build_defs.bzl @@ -24,6 +24,18 @@ def if_mkl(if_true, if_false = []): "//conditions:default": if_false }) +def if_mkl_lnx_x64(if_true, if_false = []): + """Shorthand for select()'ing on whether we're building with MKL. + + Returns a select statement which evaluates to if_true if we're building + with MKL enabled. Otherwise, the select statement evaluates to if_false. + + """ + return select({ + str(Label("//third_party/mkl:using_mkl_lnx_x64")): if_true, + "//conditions:default": if_false + }) + def _enable_local_mkl(repository_ctx): return _TF_MKL_ROOT in repository_ctx.os.environ diff --git a/third_party/mkl/mkl.BUILD b/third_party/mkl/mkl.BUILD index 8db97232e1..c3a71e4ff9 100644 --- a/third_party/mkl/mkl.BUILD +++ b/third_party/mkl/mkl.BUILD @@ -17,14 +17,29 @@ cc_library( visibility = ["//visibility:public"], ) -filegroup( - name = "libmklml_intel.so", - srcs = ["lib/libmklml_intel.so"], +cc_library( + name = "mkl_libs_linux", + srcs = [ + "lib/libiomp5.so", + "lib/libmklml_intel.so", + ], visibility = ["//visibility:public"], ) -filegroup( - name = "libiomp5.so", - srcs = ["lib/libiomp5.so"], +cc_library( + name = "mkl_libs_darwin", + srcs = [ + "lib/libiomp5.dylib", + "lib/libmklml.dylib", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "mkl_libs_windows", + srcs = [ + "lib/libiomp5md.lib", + "lib/mklml.lib", + ], visibility = ["//visibility:public"], ) -- GitLab From 828ebed1fe252339769ddc0acde83a55219b38c0 Mon Sep 17 00:00:00 2001 From: Anna R Date: Wed, 28 Mar 2018 14:42:57 -0700 Subject: [PATCH 372/906] Internal change. PiperOrigin-RevId: 190836675 --- tensorflow/python/kernel_tests/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 228d1c2452..05f34db14b 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1569,7 +1569,7 @@ cuda_py_test( cuda_py_test( name = "init_ops_test", - size = "small", + size = "medium", srcs = ["init_ops_test.py"], additional_deps = [ "//third_party/py/numpy", -- GitLab From 355c88503a3a998aef3c1dc51045409778afd578 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 14:47:00 -0700 Subject: [PATCH 373/906] Use high precision to compute softmax_cross_entropy_with_logits. PiperOrigin-RevId: 190837379 --- tensorflow/core/kernels/cwise_op_log.cc | 4 +- tensorflow/python/ops/nn_ops.py | 8 ++-- tensorflow/python/ops/nn_test.py | 51 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/tensorflow/core/kernels/cwise_op_log.cc b/tensorflow/core/kernels/cwise_op_log.cc index 98936e0f96..5d17c890cf 100644 --- a/tensorflow/core/kernels/cwise_op_log.cc +++ b/tensorflow/core/kernels/cwise_op_log.cc @@ -16,8 +16,8 @@ limitations under the License. #include "tensorflow/core/kernels/cwise_ops_common.h" namespace tensorflow { -REGISTER5(UnaryOp, CPU, "Log", functor::log, float, Eigen::half, double, - complex64, complex128); +REGISTER6(UnaryOp, CPU, "Log", functor::log, float, Eigen::half, double, + bfloat16, complex64, complex128); #if GOOGLE_CUDA REGISTER3(UnaryOp, GPU, "Log", functor::log, float, Eigen::half, double); diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index a74de39eab..0c55386241 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -1836,8 +1836,10 @@ def softmax_cross_entropy_with_logits_v2( [logits, labels]) as name: logits = ops.convert_to_tensor(logits, name="logits") labels = ops.convert_to_tensor(labels, name="labels") + convert_to_float32 = ( + logits.dtype == dtypes.float16 or logits.dtype == dtypes.bfloat16) precise_logits = math_ops.cast( - logits, dtypes.float32) if (logits.dtype == dtypes.float16) else logits + logits, dtypes.float32) if convert_to_float32 else logits # labels and logits must be of the same type labels = math_ops.cast(labels, precise_logits.dtype) input_rank = array_ops.rank(precise_logits) @@ -1883,8 +1885,8 @@ def softmax_cross_entropy_with_logits_v2( del shape[dim] cost.set_shape(shape) - if logits.dtype == dtypes.float16: - return math_ops.cast(cost, dtypes.float16) + if convert_to_float32: + return math_ops.cast(cost, logits.dtype) else: return cost diff --git a/tensorflow/python/ops/nn_test.py b/tensorflow/python/ops/nn_test.py index af9dae2aa6..da86d5f6ca 100644 --- a/tensorflow/python/ops/nn_test.py +++ b/tensorflow/python/ops/nn_test.py @@ -852,6 +852,57 @@ class ComputeSampledLogitsTest(test_lib.TestCase): self.assertAllClose(exp_sampled_softmax_loss, got_sampled_softmax_loss.eval(), 1e-4) + def testSampledSoftmaxLossBf16(self): + # A simple test to verify the numerics for bfloat16. + def _SoftmaxCrossEntropyWithLogits(logits, targets): + # logits, targets: float arrays of the same shape. + assert logits.shape == targets.shape + stable_exp_logits = np.exp( + logits - np.amax(logits, axis=1, keepdims=True)) + pred = stable_exp_logits / np.sum(stable_exp_logits, 1, keepdims=True) + return -np.sum(targets * np.log(pred + 1.0e-20), axis=1) + + np.random.seed(0) + num_classes = 5 + batch_size = 3 + labels = [0, 1, 2] + sampled = [1, 0, 2, 3] + (weights, biases, hidden_acts, _, exp_logits, + exp_labels) = self._GenerateTestData( + num_classes=num_classes, + dim=10, + batch_size=batch_size, + num_true=1, + labels=labels, + sampled=sampled, + subtract_log_q=True) + exp_sampled_softmax_loss = _SoftmaxCrossEntropyWithLogits( + exp_logits, exp_labels) + + with self.test_session(): + true_exp_bf16 = np.full( + [batch_size, 1], fill_value=0.5, dtype=dtypes.bfloat16.as_numpy_dtype) + sampled_exp_bf16 = np.full( + [len(sampled)], fill_value=0.5, dtype=dtypes.bfloat16.as_numpy_dtype) + sampled_vals_bf16 = (sampled, true_exp_bf16, sampled_exp_bf16) + + got_sampled_softmax_loss = math_ops.cast( + nn_impl.sampled_softmax_loss( + weights=constant_op.constant(weights, dtype=dtypes.bfloat16), + biases=constant_op.constant(biases, dtype=dtypes.bfloat16), + labels=constant_op.constant( + labels, shape=(batch_size, 1), dtype=dtypes.bfloat16), + inputs=constant_op.constant(hidden_acts, dtype=dtypes.bfloat16), + num_sampled=4, + num_classes=num_classes, + num_true=1, + sampled_values=sampled_vals_bf16, + remove_accidental_hits=False, + partition_strategy="div"), dtypes.float32) + + self.assertAllClose(exp_sampled_softmax_loss, + got_sampled_softmax_loss.eval(), 1e-1) + class CReluTest(test_lib.TestCase): -- GitLab From 9e6f84b6c8f1d052272d75bcde186b7f1012df48 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 14:48:50 -0700 Subject: [PATCH 374/906] Internal change PiperOrigin-RevId: 190837707 --- tensorflow/core/BUILD | 29 ++++++++++++++----- .../core/platform/default/build_config/BUILD | 5 ++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 1d11410332..4726946277 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -379,13 +379,13 @@ cc_library( ) cc_library( - name = "session_message", - srcs = ["util/session_message.cc"], - hdrs = ["util/session_message.h"], + name = "stacktrace", + srcs = glob(["platform/*/stacktrace.h"]), + hdrs = ["platform/stacktrace.h"], deps = [ - ":framework", - ":lib", - ":protos_all_cc", + ":abi", + ":lib_platform", + "//tensorflow/core/platform/default/build_config:stacktrace", ], ) @@ -394,8 +394,20 @@ cc_library( srcs = ["platform/stacktrace_handler.cc"], hdrs = ["platform/stacktrace_handler.h"], deps = [ - ":lib", + ":abi", ":lib_platform", + ":stacktrace", + ], +) + +cc_library( + name = "session_message", + srcs = ["util/session_message.cc"], + hdrs = ["util/session_message.h"], + deps = [ + ":framework", + ":lib", + ":protos_all_cc", ], ) @@ -1624,6 +1636,7 @@ cc_library( "platform/**/env_time.cc", "platform/**/cuda_libdevice_path.cc", "platform/**/device_tracer.cc", + "platform/abi.cc", "platform/variant_coding.cc", "platform/**/variant_cord_coding.cc", ], @@ -1635,6 +1648,7 @@ cc_library( "platform/**/stream_executor.h", "platform/**/env_time.cc", "platform/**/device_tracer.cc", + "platform/abi.cc", "platform/variant_coding.cc", "platform/**/variant_cord_coding.cc", ] + @@ -1648,6 +1662,7 @@ cc_library( deps = tf_additional_lib_deps() + [ ":lib_hash_crc32c_accelerate_internal", ":lib_proto_parsing", + ":abi", "//third_party/eigen3", "//tensorflow/core/platform/default/build_config:platformlib", "@snappy", diff --git a/tensorflow/core/platform/default/build_config/BUILD b/tensorflow/core/platform/default/build_config/BUILD index 2cd607edbe..afb1d84d14 100644 --- a/tensorflow/core/platform/default/build_config/BUILD +++ b/tensorflow/core/platform/default/build_config/BUILD @@ -128,6 +128,11 @@ cc_library( ], ) +cc_library( + name = "stacktrace", + srcs = [], +) + cc_library( name = "gif", copts = tf_copts(), -- GitLab From 15908d912ed26f2517207e0a0bea6cd5768476ee Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 14:52:25 -0700 Subject: [PATCH 375/906] Add DistributionStrategy support to Optimizer. PiperOrigin-RevId: 190838314 --- tensorflow/python/training/optimizer.py | 174 +++++++++++++++++++++++- 1 file changed, 172 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/training/optimizer.py b/tensorflow/python/training/optimizer.py index bf79714f96..75665fc284 100644 --- a/tensorflow/python/training/optimizer.py +++ b/tensorflow/python/training/optimizer.py @@ -35,11 +35,28 @@ from tensorflow.python.ops import state_ops from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables from tensorflow.python.training import checkpointable +from tensorflow.python.training import distribute as distribute_lib from tensorflow.python.training import slot_creator from tensorflow.python.util import nest from tensorflow.python.util.tf_export import tf_export +def get_filtered_grad_fn(grad_fn): + # `distributed_context.join()` requires that its arguments are parallel + # across threads, and in particular that `grads_and_vars` has the same + # variables in the same order. + + # When computing gradients in eager mode with multiple threads, you + # can get extra variables with a gradient of `None`. This happens when + # those variables are accessed in another thread during the gradient + # computation. To get a consistent set of variables, we filter out + # those with `None` gradients. + def filtered_grad_fn(x=None): + return [(g, v) for g, v in grad_fn(x) if g is not None] + + return filtered_grad_fn + + def _deduplicate_indexed_slices(values, indices): """Sums `values` associated with any non-unique `indices`. @@ -335,6 +352,13 @@ class Optimizer( # ... } self._deferred_slot_restorations = {} + # TODO(isaprykin): When using a DistributionStrategy, and when an + # optimizer is created in each tower, it might be dangerous to + # rely on some Optimer methods. When such methods are called on a + # per-tower optimizer, an exception needs to be thrown. We do + # allow creation per-tower optimizers however, because the + # compute_gradients()->apply_gradients() sequence is safe. + def get_name(self): return self._name @@ -447,14 +471,33 @@ class Optimizer( if var_list is not None: tape.watch(var_list) loss_value = loss() + + # Scale loss if using a "mean" loss reduction and multiple towers. + # Have to be careful to call distribute_lib.get_loss_reduction() + # *after* loss() is evaluated, so we know what loss reduction it uses. + # TODO(josh11b): Test that we handle weight decay in a reasonable way. + if distribute_lib.get_loss_reduction() == "mean": + num_towers = distribute_lib.get_distribution_strategy().num_towers + if num_towers > 1: + loss_value *= (1. / num_towers) + if var_list is None: var_list = tape.watched_variables() grads = tape.gradient(loss_value, var_list, grad_loss) return list(zip(grads, var_list)) + + # Non-callable/Tensor loss case if context.executing_eagerly(): raise RuntimeError( "`loss` passed to Optimizer.compute_gradients should " "be a function when eager execution is enabled.") + + # Scale loss if using a "mean" loss reduction and multiple towers. + if distribute_lib.get_loss_reduction() == "mean": + num_towers = distribute_lib.get_distribution_strategy().num_towers + if num_towers > 1: + loss *= (1. / num_towers) + if gate_gradients not in [Optimizer.GATE_NONE, Optimizer.GATE_OP, Optimizer.GATE_GRAPH]: raise ValueError("gate_gradients must be one of: Optimizer.GATE_NONE, " @@ -510,11 +553,25 @@ class Optimizer( Raises: TypeError: If `grads_and_vars` is malformed. ValueError: If none of the variables have gradients. + RuntimeError: If you should use `_distributed_apply()` instead. """ # This is a default implementation of apply_gradients() that can be shared # by most optimizers. It relies on the subclass implementing the following # methods: _create_slots(), _prepare(), _apply_dense(), and _apply_sparse(). + # Handle DistributionStrategy case. + if distribute_lib.get_cross_tower_context(): + raise RuntimeError("Use `_distributed_apply()` instead of " + "`apply_gradients()` in a cross-tower context.") + # TODO(isaprykin): Get rid of `has_distribution_strategy()` check by + # always calling _distributed_apply(), using the default distribution + # as needed. + if distribute_lib.has_distribution_strategy(): + grads_and_vars = get_filtered_grad_fn(lambda _: grads_and_vars)() + return distribute_lib.get_tower_context().merge_call( + self._distributed_apply, grads_and_vars, global_step, name) + + # No DistributionStrategy case. grads_and_vars = tuple(grads_and_vars) # Make sure repeat iteration works. if not grads_and_vars: raise ValueError("No variables provided.") @@ -582,6 +639,95 @@ class Optimizer( return apply_updates + def _distributed_apply(self, + distribution, + grads_and_vars, + global_step=None, + name=None): + """A version of `apply_gradients` for cross-tower context. + + This is a version of `apply_gradients()` for when you are using a + `DistributionStrategy` and are in a cross-tower context. If in a + tower context, use `apply_gradients()` as normal. + + Args: + distribution: A `DistributionStrategy` object. + grads_and_vars: List of (gradient, variable) pairs as returned by + `compute_gradients()`, and then aggregated across towers. + global_step: Optional (mirrored) `Variable` to increment by one + after the variables have been updated. + name: Optional name for the returned operation. Default to the + name passed to the `Optimizer` constructor. + + Returns: + An `Operation` that applies the specified gradients across all + towers. If `global_step` was not None, that operation also + increments `global_step`. + """ + reduced_grads = distribution.batch_reduce("sum", grads_and_vars) + var_list = [v for _, v in grads_and_vars] + grads_and_vars = zip(reduced_grads, var_list) + # Note that this is called in a cross-tower context. + self._create_slots(var_list) + + def update(v, g): + """Apply gradients to a replica variable.""" + assert v is not None + + try: + # Convert the grad to Tensor or IndexedSlices if necessary. + g = ops.convert_to_tensor_or_indexed_slices(g) + except TypeError: + raise TypeError("Gradient must be convertible to a Tensor" + " or IndexedSlices, or None: %s" % g) + if not isinstance(g, (ops.Tensor, ops.IndexedSlices)): + raise TypeError( + "Gradient must be a Tensor, IndexedSlices, or None: %s" % g) + p = _get_processor(v) + + scope_name = "" if context.executing_eagerly() else v.op.name + # device_policy is set because non-mirrored tensors will be read in + # `update_op`. `_resource_apply_dense`, `lr_t`, `beta1_t` and `beta2_t` + # is an example. + with ops.name_scope( + "update_" + scope_name), context.context().device_policy( + context.DEVICE_PLACEMENT_SILENT): + return p.update_op(self, g) + + with ops.name_scope(name, self._name) as name: + self._prepare() + + update_ops = [ + op + for grad, var in grads_and_vars + for op in distribution.unwrap(distribution.update(var, update, grad)) + ] + + def finish(self, update_ops): + return self._finish(update_ops, "update") + + non_slot_devices = distribution.non_slot_devices(var_list) + # Device policy is needed because hyperparameter tensors (such as + # AdamOptimizer's beta1_t) need to be copied across devices in Eager. + with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + finish_updates = distribution.update_non_slot( + non_slot_devices, finish, self, update_ops) + if global_step is None: + apply_updates = distribution.group(finish_updates, name=name) + else: + with ops.control_dependencies(distribution.unwrap(finish_updates)): + apply_updates = distribution.group(distribution.update( + global_step, state_ops.assign_add, 1, name=name)) + + if not context.executing_eagerly(): + if isinstance(apply_updates, ops.Tensor): + apply_updates = apply_updates.op + train_op = ops.get_collection_ref(ops.GraphKeys.TRAIN_OP) + if apply_updates not in train_op: + train_op.append(apply_updates) + + return apply_updates + def get_slot(self, var, name): """Return a slot named `name` created for `var` by the Optimizer. @@ -599,9 +745,25 @@ class Optimizer( Returns: The `Variable` for the slot if it was created, `None` otherwise. """ + # pylint: disable=protected-access named_slots = self._slots.get(name, None) if not named_slots: return None + + if hasattr(var, "_mirrored_container"): + # NOTE: If this isn't patched, then there is no `handle` in + # `_resource_apply_dense`. + mirrored_container = var._mirrored_container() + assert mirrored_container is not None + if context.executing_eagerly(): + key = mirrored_container._unique_id + else: + key = (mirrored_container.graph, mirrored_container._shared_name) + # pylint: enable=protected-access + mirrored_slot = named_slots.get(key, None) + if mirrored_slot is None: return None + return mirrored_slot.get(device=var.device) + return named_slots.get(_var_key(var), None) def get_slot_names(self): @@ -645,6 +807,7 @@ class Optimizer( def _create_non_slot_variable(self, initial_value, name, colocate_with): """Add an extra variable, not associated with a slot.""" + # Recommendation: Use OptimizerV2 if your optimizer uses non-slot variables. eager = context.executing_eagerly() graph = None if eager else colocate_with.graph @@ -652,7 +815,8 @@ class Optimizer( v = self._non_slot_dict.get(key, None) if v is None: self._maybe_initialize_checkpointable() - with ops.colocate_with(colocate_with): + distribution_strategy = distribute_lib.get_distribution_strategy() + with distribution_strategy.colocate_vars_with(colocate_with): if eager: restored_initial_value = self._preload_simple_restoration( name=name, shape=None) @@ -694,7 +858,13 @@ class Optimizer( return self._get_non_slot_variable(name, graph=graph) def _get_non_slot_variable(self, name, graph=None): - return self._non_slot_dict.get((name, graph), None) + non_slot = self._non_slot_dict.get((name, graph), None) + if hasattr(non_slot, "_mirrored_container"): + # This is a mirrored non-slot. In order to enable code like `_finish` + # to assign to a non-slot, return the current context replica. + return non_slot.get() + else: + return non_slot def _non_slot_variables(self): """Additional variables created by the `Optimizer`. -- GitLab From 82f2f084268d80c242596116f77a4224fc4e3a0e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 14:59:53 -0700 Subject: [PATCH 376/906] Automated g4 rollback of changelist 190801044 PiperOrigin-RevId: 190839672 --- .../optimizers/arithmetic_optimizer.cc | 49 ++++++++----------- .../optimizers/arithmetic_optimizer.h | 1 - .../optimizers/graph_optimizer_stage.cc | 4 -- .../optimizers/graph_optimizer_stage.h | 3 -- 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 629872bf19..5dd0b6f4b0 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -196,6 +196,8 @@ void SetSourceDataType(DataType dtype, NodeDef* node) { bool IsNumberType(DataType dtype) { return kNumberTypes.Contains(dtype); } +const char kOutputShapesAttr[] = "_output_shapes"; + // Shape is symbolically defined if it has a known rank, and each dimension is // defined, or is an unknown symbol (dim.size <= -2). bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape) { @@ -232,19 +234,16 @@ bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left, // Returns whether `reshape` is an identity op. The tensor that `reshape` // reshapes is the `output_pos`-th output of node `input`. bool ReshapeIsIdentity(const NodeDef& reshape, const NodeDef& input, - const int output_pos, - const GraphProperties& graph_properties) { - const std::vector& reshape_props = - graph_properties.GetOutputProperties(reshape.name()); - const std::vector& input_props = - graph_properties.GetOutputProperties(input.name()); - if (reshape_props.empty() || input_props.empty() || - input_props.size() <= output_pos) { + const int output_pos) { + if (!reshape.attr().count(kOutputShapesAttr) || + !input.attr().count(kOutputShapesAttr)) { return false; } - const PartialTensorShape& src_shape = input_props[output_pos].shape(); - const PartialTensorShape& dst_shape = reshape_props[0].shape(); + PartialTensorShape src_shape( + input.attr().at(kOutputShapesAttr).list().shape(output_pos)); + PartialTensorShape dst_shape( + reshape.attr().at(kOutputShapesAttr).list().shape(0)); if (src_shape.unknown_rank() || dst_shape.unknown_rank()) { return false; } @@ -1273,8 +1272,7 @@ string ArithmeticOptimizer::TrySimplifyAndReplaceUses( // outputs tensors of shape [M, N] while feeding it with tensors of shape // [M*N] (or worse). The reshape nodes are then necessary to update the // tensor metadata to the required shape. - if (can_use_shapes_ && - ReshapeIsIdentity(*reshape, *input, output_pos, *graph_properties_)) { + if (ReshapeIsIdentity(*reshape, *input, output_pos)) { return reshape->input(0); } } @@ -1588,11 +1586,11 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps() { std::vector> stages; - if (options_.combine_add_to_addn && can_use_shapes_) { + if (options_.combine_add_to_addn) { stages.push_back(std::unique_ptr( new AddOpsRewriteStage(ctx, ctx_ext))); } - if (options_.hoist_common_factor_out_of_aggregation && can_use_shapes_) { + if (options_.hoist_common_factor_out_of_aggregation) { stages.push_back(std::unique_ptr( new HoistCommonFactorOutOfAggregation(ctx, ctx_ext))); } @@ -1629,15 +1627,7 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps() { if (simplified_tensor.empty()) { for (auto& stage : stages) { if (stage->IsSupported(node)) { - const Status stage_status = - stage->TrySimplify(node, &simplified_tensor); - // Each stage must be "error safe" (just like exception safe). In - // case of any error it must leave optimized graph unmodified. - if (!stage_status.ok()) { - LOG(WARNING) << "Failed to run arithmetic optimizer stage " - << stage->stage_name() - << ". Error: " << stage_status.error_message(); - } + TF_RETURN_IF_ERROR(stage->TrySimplify(node, &simplified_tensor)); if (!simplified_tensor.empty()) { break; } @@ -1704,16 +1694,19 @@ Status ArithmeticOptimizer::Optimize(Cluster* /*cluster*/, &frame_map_, &num_frames)); // Shapes are only needed in aggressive mode. graph_properties_.reset(new GraphProperties(item)); - const Status status = graph_properties_->InferStatically(false); - can_use_shapes_ = status.ok(); - if (!can_use_shapes_) { - LOG(WARNING) << "Shape inference failed."; - } + TF_RETURN_IF_ERROR(graph_properties_->InferStatically(false)); + // TODO(ezhulenev): Use GraphProperties to lookup tensor shapes directly + TF_RETURN_IF_ERROR(graph_properties_->AnnotateOutputShapes(optimized_graph_)); // Perform the optimizations. DedupComputations(); TF_RETURN_IF_ERROR(SimplifyArithmeticOps()); + // Clear output shapes. + for (int i = 0; i < optimized_graph->node_size(); ++i) { + optimized_graph_->mutable_node(i)->mutable_attr()->erase(kOutputShapesAttr); + } + return Status::OK(); } diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h index cdeed0554e..965f0e9ea2 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h @@ -126,7 +126,6 @@ class ArithmeticOptimizer : public GraphOptimizer { RewriterConfig::Toggle opt_level_; ArithmeticOptimizerOptions options_; - bool can_use_shapes_ = false; bool fetch_nodes_known_ = false; std::unordered_set nodes_to_preserve_; std::unique_ptr node_map_; diff --git a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc index 1ea57f7b4f..7044705ade 100644 --- a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc +++ b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.cc @@ -42,10 +42,6 @@ Status GetInputNode(const GraphOptimizerContext& ctx, const string& input, Status GetTensorProperties(const GraphOptimizerContext& ctx, const string& tensor, OpInfo::TensorProperties* properties) { - if (ctx.graph_properties == nullptr) { - return errors::InvalidArgument("Graph properties are unknown."); - } - int port; string tensor_node_name = ParseNodeName(tensor, &port); if (port < 0) { diff --git a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h index c7af82abbb..be95c00d2d 100644 --- a/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h +++ b/tensorflow/core/grappler/optimizers/graph_optimizer_stage.h @@ -117,9 +117,6 @@ class GraphOptimizerStage { : optimizer_name_(optimizer_name), stage_name_(stage_name), ctx_(ctx) {} virtual ~GraphOptimizerStage() = default; - const string& stage_name() const { return stage_name_; } - const string& optimizer_name() const { return optimizer_name_; } - // Check if we should try to simplify node. Returning true doesn't // guarantee that node will be simplified. // -- GitLab From b0e79c1c029f8829de8fce18dc16388d89e50318 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 15:31:19 -0700 Subject: [PATCH 377/906] Refresh Community pages to surface new resources, SIGs and mailing lists. PiperOrigin-RevId: 190845545 --- tensorflow/docs_src/community/contributing.md | 64 +++++++++++++ tensorflow/docs_src/community/groups.md | 17 ++++ tensorflow/docs_src/community/index.md | 95 +++++++++++++++---- tensorflow/docs_src/community/leftnav_files | 5 +- tensorflow/docs_src/community/lists.md | 35 +++++++ tensorflow/docs_src/community/welcome.md | 71 -------------- 6 files changed, 198 insertions(+), 89 deletions(-) create mode 100644 tensorflow/docs_src/community/contributing.md create mode 100644 tensorflow/docs_src/community/groups.md create mode 100644 tensorflow/docs_src/community/lists.md delete mode 100644 tensorflow/docs_src/community/welcome.md diff --git a/tensorflow/docs_src/community/contributing.md b/tensorflow/docs_src/community/contributing.md new file mode 100644 index 0000000000..b0960df435 --- /dev/null +++ b/tensorflow/docs_src/community/contributing.md @@ -0,0 +1,64 @@ +# Contributing to TensorFlow + +TensorFlow is an open-source project, and we welcome your participation +and contribution. This page describes how to get involved. + +## Repositories + +The code for TensorFlow is hosted in the [TensorFlow GitHub +organization](https://github.com/tensorflow). Multiple projects are located +inside the organization, including: + +* [TensorFlow](https://github.com/tensorflow/tensorflow) +* [Models](https://github.com/tensorflow/models) +* [TensorBoard](https://github.com/tensorflow/tensorboard) +* [TensorFlow.js](https://github.com/tensorflow/tfjs) +* [TensorFlow Serving](https://github.com/tensorflow/serving) +* [TensorFlow Documentation](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/docs_src) + +## Contributor checklist + +* Before contributing to TensorFlow source code, please review the [contribution +guidelines](https://github.com/tensorflow/tensorflow/blob/master/CONTRIBUTING.md). + +* Join the +[developers@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/developers) +mailing list, to coordinate and discuss with others contributing to TensorFlow. + +* For coding style conventions, read the @{$style_guide$TensorFlow Style Guide}. + +* Finally, review @{$documentation$Writing TensorFlow Documentation}, which + explains documentation conventions. + +You may also wish to review our guide to @{$benchmarks$defining and running benchmarks}. + +## Special Interest Groups + +To enable focused collaboration on particular areas of TensorFlow, we host +Special Interest Groups (SIGs). SIGs do their work in public: if you want to +join and contribute, review the work of the group, and get in touch with the +relevant SIG leader. + +* **SIG Build** focuses on issues surrounding building, packaging, and + distribution of TensorFlow. [Mailing list](https://groups.google.com/a/tensorflow.org/forum/#!forum/build). + +* **SIG TensorBoard** furthers the development and direction of TensorBoard and its plugins. + [Mailing list](https://groups.google.com/a/tensorflow.org/forum/#!forum/tensorboard). + +* **SIG Rust** collaborates on the development of TensorFlow's Rust bindings. + [Mailing list](https://groups.google.com/a/tensorflow.org/forum/#!forum/rust). + +## Projects developed by the TensorFlow community + +The TensorFlow community has created many great projects around TensorFlow, including: + +* [Machine Learning with TensorFlow (Book & Code)](http://tensorflowbook.com) +* [@jtoy's awesome "Awesome TensorFlow" list of awesome things](https://github.com/jtoy/awesome-tensorflow) +* [TensorFlow tutorials](https://github.com/pkmital/tensorflow_tutorials) +* [Caffe to TensorFlow model converter](https://github.com/ethereon/caffe-tensorflow) +* [Bitfusion's` GPU-enabled AWS EC2 TensorFlow AMI](https://github.com/bitfusionio/amis/tree/master/awsmrkt-bfboost-ubuntu14-cuda75-tensorflow) ([Launch AMI](https://aws.amazon.com/marketplace/pp/B01EYKBEQ0)) +* [Operator Vectorization Library](https://github.com/opveclib/opveclib) +* [Swift language bindings](https://github.com/PerfectlySoft/Perfect-TensorFlow) +* [Sublime Tensorflow - A plugin for Sublime Text](https://github.com/baptisteArnaud/Sublime-Tensorflow) +* [GPflow - Gaussian processes in TensorFlow](https://github.com/GPflow/GPflow) +* [CS 20SI: Tensorflow for Deep Learning Research](https://web.stanford.edu/class/cs20si/) - please note, this course was designed with TensorFlow v0.12, so some of the notes may be out of date - but it's still a great resource. diff --git a/tensorflow/docs_src/community/groups.md b/tensorflow/docs_src/community/groups.md new file mode 100644 index 0000000000..d92f5775fa --- /dev/null +++ b/tensorflow/docs_src/community/groups.md @@ -0,0 +1,17 @@ +# User Groups + +TensorFlow has communities around the world. + +## Asia + +* [TensorFlow Korea (TF-KR) User Group](https://www.facebook.com/groups/TensorFlowKR/) _(Korean language)_ +* [TensorFlow User Group Tokyo](https://tfug-tokyo.connpass.com/) _(Japanese Language)_ +* [Soleil Data Dojo](https://soleildatadojo.connpass.com/) _(Japanese language)_ +* [TensorFlow User Group Utsunomiya](https://tfug-utsunomiya.connpass.com/) + + +## Europe + +* [TensorFlow Barcelona](https://www.meetup.com/Barcelona-Machine-Learning-Meetup/) +* [TensorFlow Madrid](https://www.meetup.com/TensorFlow-Madrid/) + diff --git a/tensorflow/docs_src/community/index.md b/tensorflow/docs_src/community/index.md index ebeff8493b..c08aeb7a97 100644 --- a/tensorflow/docs_src/community/index.md +++ b/tensorflow/docs_src/community/index.md @@ -1,18 +1,81 @@ # Community -This section contains the following documents: - - * @{$welcome$Welcome to the TensorFlow Community}, which explains how - you can get involved, where to report issues, and where to join - like-minded TensorFlow enthusiasts online. - * @{$roadmap$Roadmap}, which summarizes upcoming additions to TensorFlow. - * @{$documentation$Writing TensorFlow Documentation}, which explains - TensorFlow's documentation conventions. If you are modifying - TensorFlow source code or documentation, please read this guide. - * @{$style_guide$TensorFlow Style Guide}, which identifies coding style - conventions that TensorFlow developers and users should follow. - * @{$community/benchmarks$Benchmarks}, Benchmarks, a guide for defining and - running a TensorFlow benchmark. - * @{$security$Using TensorFlow Securely}, which explains TensorFlow's security - model, a list of recent security reports, and information on how you can - report a security vulnerability to the TensorFlow team. +Welcome to the TensorFlow community! This page explains where to get help, and +different ways to be part of the community. We are committed to fostering an +open and welcoming environment, and request that you review our [code of +conduct](https://github.com/tensorflow/tensorflow/blob/master/CODE_OF_CONDUCT.md). + +## Get Help + +### Technical Questions + +To ask or answer technical questions about TensorFlow, use [Stack +Overflow](https://stackoverflow.com/questions/tagged/tensorflow). For example, +ask or search about a particular error message you encountered during +installation. + +### Bugs and Feature Requests + +To report bugs or make feature requests, file an issue on GitHub. Please choose +the appropriate repository for the project. Major repositories include: + + * [TensorFlow](https://github.com/tensorflow/tensorflow/issues) + * [TensorBoard](https://github.com/tensorflow/tensorboard/issues) + * [TensorFlow models](https://github.com/tensorflow/models/issues) + +### Security + +Before using TensorFlow, please take a look at our security model, list of +recent security announcements, and ways you can report security issues to the +TensorFlow team at the +[Using TensorFlow Securely](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md) page on GitHub. + +## Stay Informed + +### Announcements Mailing List + +All major releases and important announcements are sent to +[announce@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/announce). +We recommend that you join this list if you depend on TensorFlow in any way. + +### Development Roadmap + +The @{$roadmap$Roadmap} summarizes plans for upcoming additions to TensorFlow. + +### Social Media + +For news and updates from around the universe of TensorFlow projects, follow +[@tensorflow](https://twitter.com/tensorflow) on Twitter. + +### YouTube + +Our [YouTube Channel](http://youtube.com/tensorflow/) focuses on machine learing +and AI with TensorFlow. On it we have a number of new shows, including: + +- TensorFlow Meets: meet with community contributors to learn and share what they're doing +- Ask TensorFlow: the team answers the best questions tagged #AskTensorFlow from social media +- Coding TensorFlow: short bites with tips for success with TensorFlow + + +## Community Support + +### Mailing Lists + +For general discussion about TensorFlow development and direction, please join +the [TensorFlow discuss mailing +list](https://groups.google.com/a/tensorflow.org/d/forum/discuss). + +A number of other mailing lists exist, focused on different project areas, which +can be found at @{$lists$TensorFlow Mailing Lists}. + +### User Groups + +To meet with like-minded people local to you, check out the many +@{$groups$TensorFlow user groups} around the world. + + +## Contributing To TensorFlow + +We welcome contributions and collaboration on TensorFlow. For more information, +please read [Contributing to TensorFlow](contributing.md). + diff --git a/tensorflow/docs_src/community/leftnav_files b/tensorflow/docs_src/community/leftnav_files index af344506c7..0bd1f14de9 100644 --- a/tensorflow/docs_src/community/leftnav_files +++ b/tensorflow/docs_src/community/leftnav_files @@ -1,7 +1,8 @@ index.md -welcome.md roadmap.md +contributing.md +lists.md +groups.md documentation.md style_guide.md benchmarks.md -security.md diff --git a/tensorflow/docs_src/community/lists.md b/tensorflow/docs_src/community/lists.md new file mode 100644 index 0000000000..dc9240030e --- /dev/null +++ b/tensorflow/docs_src/community/lists.md @@ -0,0 +1,35 @@ +# Mailing Lists + +As a community, we do much of our collaboration on public mailing lists. +Please note that if you're looking for help using TensorFlow, [Stack +Overflow](https://stackoverflow.com/questions/tagged/tensorflow) and +[GitHub issues](https://github.com/tensorflow/tensorflow/issues) +are the best initial places to look. For more information, +see [how to get help](/community/#get_help). + +## General TensorFlow lists + +* [announce](https://groups.google.com/a/tensorflow.org/forum/#!forum/announce) - Low-volume announcements of new releases. +* [discuss](https://groups.google.com/a/tensorflow.org/forum/#!forum/discuss) - General community discussion around TensorFlow. +* [developers](https://groups.google.com/a/tensorflow.org/forum/#!forum/developers) - Discussion for developers contributing to TensorFlow. + +## Project-specific lists + +These projects inside the TensorFlow GitHub organization have lists dedicated to their communities: + +* [tensor2tensor](https://groups.google.com/forum/#!forum/tensor2tensor) - User + and peer support for Tensor2Tensor. + +## Special Interest Groups + +TensorFlow's [Special Interest +Groups](/community/contributing#special_interest_groups) (SIGs) support +community collaboration on particular project focuses. Members of these groups +work together to build and support TensorFlow related projects. + +* [build](https://groups.google.com/a/tensorflow.org/forum/#!forum/build) - + Supporting SIG Build, for build, distribution and packaging of TensorFlow. +* [tensorboard](https://groups.google.com/a/tensorflow.org/forum/#!forum/tensorboard) - + Supporting SIG TensorBoard, for plugin development and other contribution. +* [rust](https://groups.google.com/a/tensorflow.org/forum/#!forum/rust) - + Supporting SIG Rust, for the Rust language bindings. diff --git a/tensorflow/docs_src/community/welcome.md b/tensorflow/docs_src/community/welcome.md deleted file mode 100644 index 6d0458e678..0000000000 --- a/tensorflow/docs_src/community/welcome.md +++ /dev/null @@ -1,71 +0,0 @@ -# Welcome to the TensorFlow Community - -TensorFlow is an open-source project. This page explains how to contribute, -where to ask questions, and how to help each other. - - -## Development - -The source code for TensorFlow is on -[GitHub](https://github.com/tensorflow/tensorflow). - -Before contributing to TensorFlow source code, please review the -[Contribution guidelines](https://github.com/tensorflow/tensorflow/blob/master/CONTRIBUTING.md). - -### Projects developed by the TensorFlow community - -The TensorFlow community has created many great projects around TensorFlow, including: - -* [Machine Learning with TensorFlow (Book & Code)](http://tensorflowbook.com) -* [@jtoy's awesome "Awesome TensorFlow" list of awesome things](https://github.com/jtoy/awesome-tensorflow) -* [TensorFlow tutorials](https://github.com/pkmital/tensorflow_tutorials) -* [Caffe to TensorFlow model converter](https://github.com/ethereon/caffe-tensorflow) -* [Bitfusion's` GPU-enabled AWS EC2 TensorFlow AMI](https://github.com/bitfusionio/amis/tree/master/awsmrkt-bfboost-ubuntu14-cuda75-tensorflow) ([Launch AMI](https://aws.amazon.com/marketplace/pp/B01EYKBEQ0)) -* [Rust language bindings](https://github.com/google/tensorflow-rust) -* [Operator Vectorization Library](https://github.com/opveclib/opveclib) -* [Swift language bindings](https://github.com/PerfectlySoft/Perfect-TensorFlow) -* [Sublime Tensorflow - A plugin for Sublime Text](https://github.com/baptisteArnaud/Sublime-Tensorflow) -* [Edward - A library for probabilistic modeling, inference, and criticism](http://edwardlib.org) ([Github](https://github.com/blei-lab/edward), [Forum](https://discourse.edwardlib.org)) -* [GPflow - Gaussian processes in TensorFlow](https://github.com/GPflow/GPflow) -* [CS 20SI: Tensorflow for Deep Learning Research](https://web.stanford.edu/class/cs20si/) - Please note, this course was designed with TensorFlow v0.12, so some of the notes may be out of date - but it's still a great resource. - -## TensorFlow Communities Around the World - -Asia: - -* [TensorFlow Korea (TF-KR) User Group](https://www.facebook.com/groups/TensorFlowKR/) _(Korean language)_ -* [TensorFlow User Group Tokyo](https://tfug-tokyo.connpass.com/) _(Japanese Language)_ -* [Soleil Data Dojo](https://soleildatadojo.connpass.com/) _(Japanese language)_ -* [TensorFlow User Group Utsunomiya](https://tfug-utsunomiya.connpass.com/) - - -Europe: - -* [TensorFlow Barcelona](https://www.meetup.com/Barcelona-Machine-Learning-Meetup/) -* [TensorFlow Madrid](https://www.meetup.com/TensorFlow-Madrid/) - - - -## Support - -TensorFlow provides multiple communication paths. To pick the right path, -please read the following list carefully: - - * For new release announcements and security updates, subscribe to - [announce@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/announce). - * To ask or answer technical questions about TensorFlow, use - [Stack Overflow](https://stackoverflow.com/questions/tagged/tensorflow). - For example, ask or search Stack Overflow about a particular error message - you encountered during installation. - * To join general discussions about TensorFlow development and directions, - please join the - [TensorFlow discuss mailing list](https://groups.google.com/a/tensorflow.org/d/forum/discuss). - For example, use this mailing list to learn about new features in - upcoming releases of TensorFlow. - * To report bugs or make feature requests, use the - [TensorFlow issues tracker](https://github.com/tensorflow/tensorflow/issues) - on GitHub. For example, use the issue tracker to request a - new operation in TensorFlow. - * To report vulnerabilities, please follow our - [vulnerability disclosure guidelines](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md). - -- GitLab From 6cb3e6e0988a7bd123e683c13dae8470c71822af Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Wed, 28 Mar 2018 15:54:31 -0700 Subject: [PATCH 378/906] [tf.data] Expose the symbol `tf.contrib.data.make_csv_dataset()`. PiperOrigin-RevId: 190849333 --- tensorflow/contrib/data/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/contrib/data/__init__.py b/tensorflow/contrib/data/__init__.py index 7c3a9f82ff..17048314a4 100644 --- a/tensorflow/contrib/data/__init__.py +++ b/tensorflow/contrib/data/__init__.py @@ -32,6 +32,7 @@ See the @{$datasets$Importing Data} Programmer's Guide for an overview. @@group_by_window @@ignore_errors @@make_batched_features_dataset +@@make_csv_dataset @@make_saveable_from_iterator @@map_and_batch @@padded_batch_and_drop_remainder @@ -70,6 +71,7 @@ from tensorflow.contrib.data.python.ops.interleave_ops import sloppy_interleave from tensorflow.contrib.data.python.ops.iterator_ops import make_saveable_from_iterator from tensorflow.contrib.data.python.ops.prefetching_ops import prefetch_to_device from tensorflow.contrib.data.python.ops.readers import make_batched_features_dataset +from tensorflow.contrib.data.python.ops.readers import make_csv_dataset from tensorflow.contrib.data.python.ops.readers import read_batch_features from tensorflow.contrib.data.python.ops.readers import SqlDataset from tensorflow.contrib.data.python.ops.resampling import rejection_resample -- GitLab From 830c19c3f20816dcb5e8e9b6cb51f63cf8461442 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 16:12:51 -0700 Subject: [PATCH 379/906] Add IsSquare bool to the grappler op_types. PiperOrigin-RevId: 190852501 --- tensorflow/core/grappler/op_types.cc | 2 ++ tensorflow/core/grappler/op_types.h | 1 + 2 files changed, 3 insertions(+) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 1a6751befc..c31ac9b59c 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -309,6 +309,8 @@ bool IsSplitV(const NodeDef& node) { return node.op() == "SplitV"; } bool IsSqrtGrad(const NodeDef& node) { return node.op() == "SqrtGrad"; } +bool IsSquare(const NodeDef& node) { return node.op() == "Square"; } + bool IsSquaredDifference(const NodeDef& node) { return node.op() == "SquaredDifference"; } diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index 1ec1cd46e3..39affcbc24 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -121,6 +121,7 @@ bool IsSoftsignGrad(const NodeDef& node); bool IsSplit(const NodeDef& node); bool IsSplitV(const NodeDef& node); bool IsSqrtGrad(const NodeDef& node); +bool IsSquare(const NodeDef& node); bool IsSquaredDifference(const NodeDef& node); bool IsSqueeze(const NodeDef& node); bool IsStackOp(const NodeDef& node); -- GitLab From 390e19ab990f5656e09d98624c92b3c80e52937d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 16:16:48 -0700 Subject: [PATCH 380/906] Tower-local variable support for DistributionStrategy. Each tower has its own variable, but fetch() and checkpoint apply a reduction to get a single value. PiperOrigin-RevId: 190853123 --- tensorflow/python/training/distribute.py | 59 +++++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/tensorflow/python/training/distribute.py b/tensorflow/python/training/distribute.py index 757ba71c4a..f98872775a 100644 --- a/tensorflow/python/training/distribute.py +++ b/tensorflow/python/training/distribute.py @@ -126,16 +126,18 @@ class UpdateContext(object): def get_tower_context(): - """Returns the current TowerContext or None. + """Returns the current TowerContext or None if in a cross-tower context. Note that execution: - 1. starts in the default (single-tower) tower context; - 2. switches to cross-tower context when entering a - `with DistributionStrategy.scope():` block; + 1. starts in the default (single-tower) tower context (this function + will return the default TowerContext object); + 2. switches to cross-tower context (in which case this will return + None) when entering a `with DistributionStrategy.scope():` block; 3. switches to a (non-default) tower context inside `call_for_each_tower(fn, ...)`; 4. if `fn` calls `get_tower_context()->merge_call(merge_fn, ...)`, then - inside `merge_fn` you are back in the cross-tower context. + inside `merge_fn` you are back in the cross-tower context (and again + this function will return None). Note that you can also go directly from step 1 to 4 to switch to a cross-tower context for the default `DistributionStrategy`. You may @@ -188,6 +190,9 @@ def get_cross_tower_context(): def get_distribution_strategy(): """Returns the current `DistributionStrategy` object. + Prefer to use `get_tower_context()` or `get_cross_tower_context()` + instead when possible. + Returns: A `DistributionStrategy` object. Inside a `with distribution_strategy.scope()` block, it returns @@ -526,7 +531,6 @@ class DistributionStrategy(object): # TODO(josh11b): ClusterSpec/ClusterResolver # TODO(josh11b): Partitioned computations, state; sharding # TODO(josh11b): Model parallelism: "towers" with multiple devices; shuffling - # TODO(josh11b): Tower-local variables # TODO(josh11b): List of towers with their worker and parameter devices # (where the parameter devices may overlap in the ps case). @@ -556,6 +560,43 @@ class DistributionStrategy(object): # Note: should support "colocate_with" argument. raise NotImplementedError("must be implemented in descendants") + def tower_local_var_scope(self, reduce_method): + """Inside this scope, new variables will not be mirrored. + + There will still be one component variable per tower, but there is + no requirement that they stay in sync. Instead, when saving them + or calling `fetch()`, we use the value that results when calling + `reduce()` on all the towers' variables. + + Note: tower-local implies not trainable. Instead, it is expected + that each tower will directly update (using `assign_add()` or + whatever) its local variable instance but only the aggregated + value (accessible using `fetch()`) will be exported from the + model. When it is acceptable to only aggregate on export, we + greatly reduce communication overhead by using tower-local + variables. + + Note: All component variables will be initialized to the same + value, using the initialization expression from the first tower. + The values will match even if the initialization expression uses + random numbers. + + Args: + reduce_method: String used as a `method_string` to `reduce()` + to get the value to save when checkpointing. + + Returns: + A context manager. + """ + def create_tower_local_variable(next_creator, *args, **kwargs): + _require_distribution_strategy_scope(self) + kwargs["use_resource"] = True + kwargs["tower_local_reduce_method"] = reduce_method + return next_creator(*args, **kwargs) + + _require_distribution_strategy_scope(self) + return variable_scope.variable_creator_scope(create_tower_local_variable) + def colocate_vars_with(self, colocate_with_variable): """Scope that controls which devices variables will be created on. @@ -984,6 +1025,10 @@ class TowerContext(object): finally: _pop_per_thread_mode() + def tower_local_var_scope(self, reduce_method): + """Alias for distribution_strategy.tower_local_var_scope().""" + return self._distribution_strategy.tower_local_var_scope(reduce_method) + @property def is_single_tower(self): """Returns whether there is a single tower or multiple.""" @@ -1030,6 +1075,8 @@ class _DefaultDistributionStrategy(DistributionStrategy): def creator(next_creator, *args, **kwargs): _require_distribution_strategy_scope(self) + if kwargs.pop("tower_local_reduce_method", None) is not None: + kwargs["trainable"] = False return next_creator(*args, **kwargs) return _CurrentDistributionContext( -- GitLab From 108178da2a20ea2d3899417ee932d46ba1a5c652 Mon Sep 17 00:00:00 2001 From: Anna R Date: Wed, 28 Mar 2018 16:52:39 -0700 Subject: [PATCH 381/906] Automated g4 rollback of changelist 190835392 PiperOrigin-RevId: 190858242 --- RELEASE.md | 60 --- configure.py | 2 +- tensorflow/BUILD | 7 - tensorflow/contrib/BUILD | 27 +- .../boosted_trees/kernels/quantile_ops.cc | 2 +- .../boosted_trees/lib/utils/batch_features.cc | 2 +- .../lib/utils/batch_features_test.cc | 2 +- .../boosted_trees/lib/utils/dropout_utils.cc | 2 +- .../boosted_trees/lib/utils/dropout_utils.h | 2 +- .../lib/utils/sparse_column_iterable_test.cc | 2 +- .../boosted_trees/proto/tree_config.proto | 2 +- .../kernel_tests/prediction_ops_test.py | 10 +- .../python/kernel_tests/quantile_ops_test.py | 2 +- .../boosted_trees/python/ops/quantile_ops.py | 2 +- tensorflow/contrib/cmake/tf_tests.cmake | 3 - .../kernel_tests/batch_dataset_op_test.py | 14 - tensorflow/contrib/eager/python/BUILD | 6 +- .../eager/python/examples/spinn/spinn_test.py | 1 + .../python/estimator/replicate_model_fn.py | 2 +- .../factorization/kernels/clustering_ops.cc | 2 +- .../python/ops/factorization_ops.py | 14 +- .../python/ops/factorization_ops_test.py | 12 +- .../factorization/python/ops/gmm_ops.py | 4 +- .../factorization/python/ops/gmm_test.py | 2 +- .../factorization/python/ops/kmeans_test.py | 4 +- .../contrib/factorization/python/ops/wals.py | 2 +- tensorflow/contrib/learn/BUILD | 1 - .../learn/python/learn/estimators/linear.py | 4 +- .../linear_optimizer/python/sdca_estimator.py | 4 +- tensorflow/contrib/lite/README.md | 3 - tensorflow/contrib/lite/builtin_ops.h | 1 - tensorflow/contrib/lite/g3doc/models.md | 2 +- tensorflow/contrib/lite/kernels/BUILD | 13 - .../internal/reference/reference_ops.h | 25 -- tensorflow/contrib/lite/kernels/maximum.cc | 106 ----- .../contrib/lite/kernels/maximum_test.cc | 81 ---- tensorflow/contrib/lite/kernels/register.cc | 2 - tensorflow/contrib/lite/model.cc | 3 - tensorflow/contrib/lite/nnapi_delegate.cc | 1 - tensorflow/contrib/lite/python/lite.py | 22 +- tensorflow/contrib/lite/schema/schema.fbs | 5 - .../contrib/lite/schema/schema_generated.h | 124 +----- tensorflow/contrib/lite/testing/BUILD | 1 - .../contrib/lite/testing/generate_examples.py | 36 -- .../testing/generated_examples_zip_test.cc | 1 - .../contrib/lite/toco/tflite/operator.cc | 2 - .../contrib/lite/toco/tflite/operator_test.cc | 2 - tensorflow/contrib/lookup/lookup_ops.py | 2 +- .../contrib/makefile/download_dependencies.sh | 2 +- tensorflow/contrib/makefile/tf_op_files.txt | 1 - .../seq2seq/kernels/beam_search_ops.cc | 2 +- .../seq2seq/python/ops/attention_wrapper.py | 8 +- .../seq2seq/python/ops/beam_search_decoder.py | 6 +- .../slim/python/slim/data/parallel_reader.py | 4 +- .../slim/python/slim/data/prefetch_queue.py | 4 +- .../python/slim/data/tfexample_decoder.py | 2 +- tensorflow/contrib/tensorrt/README.md | 46 +-- .../contrib/tensorrt/convert/convert_graph.cc | 20 +- .../contrib/tensorrt/convert/convert_nodes.cc | 375 ++++++++---------- .../contrib/tensorrt/segment/segment.cc | 55 +-- tensorflow/contrib/tensorrt/segment/segment.h | 4 +- .../contrib/tensorrt/segment/segment_test.cc | 8 +- .../timeseries/python/timeseries/ar_model.py | 2 +- .../python/timeseries/math_utils.py | 2 +- .../timeseries/state_space_models/varma.py | 4 +- .../base_api/api_def_MatrixSolveLs.pbtxt | 6 +- .../core/common_runtime/mkl_cpu_allocator.cc | 3 + tensorflow/core/framework/common_shape_fns.cc | 4 +- tensorflow/core/framework/common_shape_fns.h | 8 +- tensorflow/core/framework/shape_inference.h | 1 - .../core/kernels/mkl_fused_batch_norm_op.cc | 2 +- .../core/kernels/segment_reduction_ops.h | 7 - tensorflow/core/kernels/snapshot_op.cc | 30 -- tensorflow/core/kernels/snapshot_op.h | 26 +- tensorflow/core/kernels/snapshot_op_gpu.cu.cc | 9 +- tensorflow/core/kernels/xent_op.cc | 65 +-- tensorflow/core/kernels/xent_op.h | 35 +- tensorflow/core/kernels/xent_op_gpu.cu.cc | 9 +- tensorflow/core/ops/array_ops.cc | 26 +- tensorflow/core/ops/nn_ops.cc | 23 +- tensorflow/core/ops/nn_ops_test.cc | 16 +- tensorflow/core/public/version.h | 4 +- .../python/contrib.bayesflow.monte_carlo.md | 36 +- .../api_guides/python/contrib.losses.md | 28 +- .../docs_src/community/documentation.md | 38 +- tensorflow/docs_src/install/install_c.md | 2 +- tensorflow/docs_src/install/install_go.md | 2 +- tensorflow/docs_src/install/install_java.md | 22 +- tensorflow/docs_src/install/install_linux.md | 22 +- tensorflow/docs_src/install/install_mac.md | 14 +- .../docs_src/install/install_sources.md | 9 +- tensorflow/docs_src/mobile/optimizing.md | 2 - tensorflow/docs_src/mobile/prepare_models.md | 2 +- tensorflow/python/BUILD | 2 +- .../python/kernel_tests/array_ops_test.py | 26 +- tensorflow/python/kernel_tests/testdata/BUILD | 2 +- .../python/kernel_tests/xent_op_test.py | 81 +--- tensorflow/python/layers/convolutional.py | 2 - .../python/layers/convolutional_test.py | 6 - tensorflow/python/ops/linalg_ops.py | 2 +- .../python/training/monitored_session.py | 33 +- .../python/training/monitored_session_test.py | 36 -- tensorflow/tensorflow.bzl | 4 +- .../tools/api/golden/tensorflow.train.pbtxt | 2 +- .../tools/ci_build/osx/libtensorflow_cpu.sh | 2 +- tensorflow/tools/docker/Dockerfile.devel | 2 +- .../tools/docker/Dockerfile.devel-cpu-mkl | 2 +- tensorflow/tools/docker/Dockerfile.devel-gpu | 2 +- tensorflow/tools/lib_package/BUILD | 2 + tensorflow/tools/pip_package/BUILD | 1 + tensorflow/tools/pip_package/setup.py | 6 +- tensorflow/workspace.bzl | 133 +++---- third_party/mkl/BUILD | 46 +-- third_party/mkl/MKL_LICENSE | 201 ---------- third_party/mkl/build_defs.bzl | 12 - third_party/mkl/mkl.BUILD | 27 +- 116 files changed, 556 insertions(+), 1703 deletions(-) delete mode 100644 tensorflow/contrib/lite/kernels/maximum.cc delete mode 100644 tensorflow/contrib/lite/kernels/maximum_test.cc delete mode 100644 third_party/mkl/MKL_LICENSE diff --git a/RELEASE.md b/RELEASE.md index c63d9f20c9..6f54dee58f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,63 +1,3 @@ -# Release 1.7.0 - -## Major Features And Improvements -* Eager mode is moving out of contrib, try `tf.enable_eager_execution()`. -* Graph rewrites emulating fixed-point quantization compatible with TensorFlow Lite, supported by new `tf.contrib.quantize` package. -* Easily customize gradient computation with `tf.custom_gradient`. -* [TensorBoard Debugger Plugin](https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/debugger/README.md), the graphical user interface (GUI) of TensorFlow Debugger (tfdbg), is now in alpha. -* Experimental support for reading a sqlite database as a `Dataset` with new `tf.contrib.data.SqlDataset`. -* Distributed Mutex / CriticalSection added to `tf.contrib.framework.CriticalSection`. -* Better text processing with `tf.regex_replace`. -* Easy, efficient sequence input with `tf.contrib.data.bucket_by_sequence_length` - -## Bug Fixes and Other Changes -* Accelerated Linear Algebra (XLA): - * Add `MaxPoolGradGrad` support for XLA - * CSE pass from Tensorflow is now disabled in XLA. -* `tf.data`: - * `tf.data.Dataset` - * Add support for building C++ Dataset op kernels as external libraries, using the `tf.load_op_library()` mechanism. - * `Dataset.list_files()` now shuffles its output by default. - * `Dataset.shuffle(..., seed=tf.constant(0, dtype=tf.int64))` now yields the same sequence of elements as `Dataset.shuffle(..., seed=0)`. - * Add `num_parallel_reads` argument to `tf.data.TFRecordDataset`. -* `tf.contrib`: - * `tf.contrib.bayesflow.halton_sequence` now supports randomization. - * Add support for scalars in `tf.contrib.all_reduce`. - * Add `effective_sample_size` to `tf.contrib.bayesflow.mcmc_diagnostics`. - * Add `potential_scale_reduction` to `tf.contrib.bayesflow.mcmc_diagnostics`. - * Add `BatchNormalization`, `Kumaraswamy` bijectors. - * Deprecate `tf.contrib.learn`. Please check contrib/learn/README.md for instructions on how to convert existing code. - * `tf.contrib.data` - * Remove deprecated `tf.contrib.data.Dataset`, `tf.contrib.data.Iterator`, `tf.contrib.data.FixedLengthRecordDataset`, `tf.contrib.data.TextLineDataset`, and `tf.contrib.data.TFRecordDataset` classes. - * Added `bucket_by_sequence_length`, `sliding_window_batch`, and `make_batched_features_dataset` - * Remove unmaintained `tf.contrib.ndlstm`. You can find it externally at https://github.com/tmbarchive/tfndlstm. - * Moved most of `tf.contrib.bayesflow` to its own repo: `tfp` -* Other: - * tf.py_func now reports the full stack trace if an exception occurs. - * Integrate `TPUClusterResolver` with GKE's integration for Cloud TPUs. - * Add a library for statistical testing of samplers. - * Add Helpers to stream data from the GCE VM to a Cloud TPU. - * Integrate ClusterResolvers with TPUEstimator. - * Unify metropolis_hastings interface with HMC kernel. - * Move LIBXSMM convolutions to a separate --define flag so that they are disabled by default. - * Fix `MomentumOptimizer` lambda. - * Reduce `tfp.layers` boilerplate via programmable docstrings. - * Add `auc_with_confidence_intervals`, a method for computing the AUC and confidence interval with linearithmic time complexity. - * `regression_head` now accepts customized link function, to satisfy the usage that user can define their own link function if the `array_ops.identity` does not meet the requirement. - * Fix `initialized_value` and `initial_value` behaviors for `ResourceVariables` created from `VariableDef` protos. - * Add TensorSpec to represent the specification of Tensors. - * Constant folding pass is now deterministic. - * Support `float16` `dtype` in `tf.linalg.*`. - * Add `tf.estimator.export.TensorServingInputReceiver` that allows `tf.estimator.Estimator.export_savedmodel` to pass raw tensors to model functions. - -## Thanks to our Contributors - -This release contains contributions from many people at Google, as well as: - -4d55397500, Abe, Alistair Low, Andy Kernahan, Appledore, Ben, Ben Barsdell, Boris Pfahringer, Brad Wannow, Brett Koonce, Carl Thomé, cclauss, Chengzhi Chen, Chris Drake, Christopher Yeh, Clayne Robison, Codrut Grosu, Daniel Trebbien, Danny Goodman, David Goodwin, David Norman, Deron Eriksson, Donggeon Lim, Donny Viszneki, DosLin, DylanDmitri, Francisco Guerrero, Fred Reiss, gdh1995, Giuseppe, Glenn Weidner, gracehoney, Guozhong Zhuang, Haichen "Hc" Li, Harald Husum, harumitsu.nobuta, Henry Spivey, hsm207, Jekyll Song, Jerome, Jiongyan Zhang, jjsjann123, John Sungjin Park, Johnson145, JoshVarty, Julian Wolff, Jun Wang, June-One, Kamil Sindi, Kb Sriram, Kdavis-Mozilla, Kenji, lazypanda1, Liang-Chi Hsieh, Loo Rong Jie, Mahesh Bhosale, MandarJKulkarni, ManHyuk, Marcus Ong, Marshal Hayes, Martin Pool, matthieudelaro, mdfaijul, mholzel, Michael Zhou, Ming Li, Minmin Sun, Myungjoo Ham, MyungsungKwak, Naman Kamra, Peng Yu, Penghao Cen, Phil, Raghuraman-K, resec, Rohin Mohanadas, Sandeep N Gupta, Scott Tseng, seaotterman, Seo Sanghyeon, Sergei Lebedev, Ted Chang, terrytangyuan, Tim H, tkunic, Tod, vihanjain, Yan Facai (颜发才), Yin Li, Yong Tang, Yukun Chen, Yusuke Yamada - - - # Release 1.6.0 ## Breaking Changes diff --git a/configure.py b/configure.py index 0f52c0ec99..22b9abedd7 100644 --- a/configure.py +++ b/configure.py @@ -1414,7 +1414,7 @@ def main(): set_build_var(environ_cp, 'TF_NEED_S3', 'Amazon S3 File System', 'with_s3_support', True, 's3') set_build_var(environ_cp, 'TF_NEED_KAFKA', 'Apache Kafka Platform', - 'with_kafka_support', True, 'kafka') + 'with_kafka_support', False, 'kafka') set_build_var(environ_cp, 'TF_ENABLE_XLA', 'XLA JIT', 'with_xla_support', False, 'xla') set_build_var(environ_cp, 'TF_NEED_GDR', 'GDR', 'with_gdr_support', diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 29a01efc84..6ab43638ba 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -240,13 +240,6 @@ config_setting( visibility = ["//visibility:public"], ) -config_setting( - name = "with_kafka_support_windows_override", - define_values = {"with_kafka_support": "true"}, - values = {"cpu": "x64_windows"}, - visibility = ["//visibility:public"], -) - config_setting( name = "with_gcp_support_android_override", define_values = {"with_gcp_support": "true"}, diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index fb81b50fe8..bdbd738906 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -51,6 +51,7 @@ py_library( "//tensorflow/contrib/image:single_image_random_dot_stereograms_py", "//tensorflow/contrib/input_pipeline:input_pipeline_py", "//tensorflow/contrib/integrate:integrate_py", + "//tensorflow/contrib/kafka", "//tensorflow/contrib/keras", "//tensorflow/contrib/kernel_methods", "//tensorflow/contrib/kfac", @@ -109,13 +110,7 @@ py_library( "//tensorflow/python:util", ] + if_mpi(["//tensorflow/contrib/mpi_collectives:mpi_collectives_py"]) + if_tensorrt([ "//tensorflow/contrib/tensorrt:init_py", - ]) + select({ - "//tensorflow:with_kafka_support_windows_override": [], - "//tensorflow:with_kafka_support": [ - "//tensorflow/contrib/kafka", - ], - "//conditions:default": [], - }), + ]), ) cc_library( @@ -125,6 +120,7 @@ cc_library( "//tensorflow/contrib/boosted_trees:boosted_trees_kernels", "//tensorflow/contrib/coder:all_kernels", "//tensorflow/contrib/data/kernels:dataset_kernels", + "//tensorflow/contrib/kafka:dataset_kernels", "//tensorflow/contrib/factorization/kernels:all_kernels", "//tensorflow/contrib/input_pipeline:input_pipeline_ops_kernels", "//tensorflow/contrib/layers:sparse_feature_cross_op_kernel", @@ -137,13 +133,7 @@ cc_library( "//tensorflow/contrib/text:all_kernels", ] + if_mpi(["//tensorflow/contrib/mpi_collectives:mpi_collectives_py"]) + if_cuda([ "//tensorflow/contrib/nccl:nccl_kernels", - ]) + select({ - "//tensorflow:with_kafka_support_windows_override": [], - "//tensorflow:with_kafka_support": [ - "//tensorflow/contrib/kafka:dataset_kernels", - ], - "//conditions:default": [], - }), + ]), ) cc_library( @@ -156,6 +146,7 @@ cc_library( "//tensorflow/contrib/factorization:all_ops", "//tensorflow/contrib/framework:all_ops", "//tensorflow/contrib/input_pipeline:input_pipeline_ops_op_lib", + "//tensorflow/contrib/kafka:dataset_ops_op_lib", "//tensorflow/contrib/layers:sparse_feature_cross_op_op_lib", "//tensorflow/contrib/nccl:nccl_ops_op_lib", "//tensorflow/contrib/nearest_neighbor:nearest_neighbor_ops_op_lib", @@ -166,13 +157,7 @@ cc_library( "//tensorflow/contrib/tensor_forest:tensor_forest_ops_op_lib", "//tensorflow/contrib/text:all_ops", "//tensorflow/contrib/tpu:all_ops", - ] + select({ - "//tensorflow:with_kafka_support_windows_override": [], - "//tensorflow:with_kafka_support": [ - "//tensorflow/contrib/kafka:dataset_ops_op_lib", - ], - "//conditions:default": [], - }), + ], ) filegroup( diff --git a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc index 0b28f81e7c..0f4c2298f5 100644 --- a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc +++ b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc @@ -253,7 +253,7 @@ class CreateQuantileAccumulatorOp : public OpKernel { private: float epsilon_; int32 num_quantiles_; - // An upper bound on the number of entries that the summaries might have + // An upperbound on the number of enteries that the summaries might have // for a feature. int64 max_elements_; bool generate_quantiles_; diff --git a/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc b/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc index 35b059f349..cf4f9a097a 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/batch_features.cc @@ -54,7 +54,7 @@ Status BatchFeatures::Initialize( TF_CHECK_AND_RETURN_IF_ERROR( dense_float_feature.dim_size(1) == 1, errors::InvalidArgument( - "Dense float features may not be multivalent: dim_size(1) = ", + "Dense float features may not be multi-valent: dim_size(1) = ", dense_float_feature.dim_size(1))); dense_float_feature_columns_.emplace_back(dense_float_feature); } diff --git a/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc b/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc index cfe9101e74..609519e8b1 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/batch_features_test.cc @@ -59,7 +59,7 @@ TEST_F(BatchFeaturesTest, DenseFloatFeatures_Multivalent) { BatchFeatures batch_features(1); auto dense_vec = AsTensor({3.0f, 7.0f}, {1, 2}); auto expected_error = InvalidArgument( - "Dense float features may not be multivalent: dim_size(1) = 2"); + "Dense float features may not be multi-valent: dim_size(1) = 2"); EXPECT_EQ(expected_error, batch_features.Initialize({dense_vec}, {}, {}, {}, {}, {}, {})); } diff --git a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc index ce67db797d..db34db998a 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.cc @@ -54,7 +54,7 @@ Status DropoutUtils::DropOutTrees( if (probability_of_skipping_dropout < 0 || probability_of_skipping_dropout > 1) { return errors::InvalidArgument( - "Probability of skipping dropout must be in [0,1] range"); + "Probability of skiping dropout must be in [0,1] range"); } const auto num_trees = weights.size(); diff --git a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h index 77c16da541..928bfbfe5c 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h +++ b/tensorflow/contrib/boosted_trees/lib/utils/dropout_utils.h @@ -66,7 +66,7 @@ class DropoutUtils { // Current weights and num_updates will be updated as a result of this // func std::vector* current_weights, - // How many weight assignments have been done for each tree already. + // How many weight assignements have been done for each tree already. std::vector* num_updates); }; diff --git a/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc b/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc index cc7604745e..0138aae3db 100644 --- a/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc +++ b/tensorflow/contrib/boosted_trees/lib/utils/sparse_column_iterable_test.cc @@ -34,7 +34,7 @@ TEST_F(SparseColumnIterableTest, Empty) { } TEST_F(SparseColumnIterableTest, Iterate) { - // 8 examples having 7 sparse features with the 3rd and 7th multivalent. + // 8 examples having 7 sparse features with the 3rd and 7th multi-valent. // This can be visualized like the following: // Instance | Sparse | // 0 | x | diff --git a/tensorflow/contrib/boosted_trees/proto/tree_config.proto b/tensorflow/contrib/boosted_trees/proto/tree_config.proto index 81411aa84a..4407c4d981 100644 --- a/tensorflow/contrib/boosted_trees/proto/tree_config.proto +++ b/tensorflow/contrib/boosted_trees/proto/tree_config.proto @@ -53,7 +53,7 @@ message DenseFloatBinarySplit { // Float feature column and split threshold describing // the rule feature <= threshold. int32 feature_column = 1; - // If feature column is multivalent, this holds the index of the dimension + // If feature column is multivalent, this holds the index of the dimensiong // for the split. Defaults to 0. int32 dimension_id = 5; float threshold = 2; diff --git a/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py b/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py index cf55759aaa..c1acf35160 100644 --- a/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py +++ b/tensorflow/contrib/boosted_trees/python/kernel_tests/prediction_ops_test.py @@ -120,8 +120,8 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): """Sets up the prediction tests. Create a batch of two examples having one dense float, two sparse float - single valued, one sparse float multidimensional and one sparse int - features. The data looks like the following: + single valued, one sparse float multidimensionl and one sparse int features. + The data looks like the following: | Instance | Dense0 | SparseF0 | SparseF1 | SparseI0 | SparseM | 0 | 7 | -3 | | 9,1 | __, 5.0 | 1 | -2 | | 4 | | 3, ___ @@ -810,7 +810,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): # building. This tree should never be dropped. num_trees = 10 with self.test_session(): - # Empty tree ensemble. + # Empty tree ensenble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() # Add 10 trees with some weights. for i in range(0, num_trees): @@ -951,7 +951,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): def testDropOutZeroProb(self): with self.test_session(): - # Empty tree ensemble. + # Empty tree ensenble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() # Add 1000 trees with some weights. for i in range(0, 999): @@ -994,7 +994,7 @@ class PredictionOpsTest(test_util.TensorFlowTestCase): def testAveragingAllTrees(self): with self.test_session(): - # Empty tree ensemble. + # Empty tree ensenble. tree_ensemble_config = tree_config_pb2.DecisionTreeEnsembleConfig() adjusted_tree_ensemble_config = ( tree_config_pb2.DecisionTreeEnsembleConfig()) diff --git a/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py b/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py index 074623699d..81f58de28c 100644 --- a/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py +++ b/tensorflow/contrib/boosted_trees/python/kernel_tests/quantile_ops_test.py @@ -482,7 +482,7 @@ class QuantilesOpTest(test_util.TensorFlowTestCase): """Sets up the quantile op tests. Create a batch of 4 examples having 2 dense and 4 sparse features. - Fourth sparse feature is multivalent (3 dimensional) + Forth sparse feature is multivalent (3 dimensional) The data looks like this | Instance | Dense 0 | Dense 1 | Sparse 0 | Sparse 1 |Sparse 2| SparseM | 0 | -0.1 | -1 | -2 | 0.1 | |_ ,1,_ diff --git a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py index 1b184d296b..97d57e8b23 100644 --- a/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py +++ b/tensorflow/contrib/boosted_trees/python/ops/quantile_ops.py @@ -184,7 +184,7 @@ class QuantileAccumulator(saver.BaseSaverBuilder.SaveableObject): """Finalizes quantile summary stream and resets it for next iteration. Args: - stamp_token: Expected current token. + stamp_token: Exepcted current token. next_stamp_token: Next value for the token. Returns: A list of quantiles or approximate boundaries. diff --git a/tensorflow/contrib/cmake/tf_tests.cmake b/tensorflow/contrib/cmake/tf_tests.cmake index 92f2ab6dea..f793877c8b 100644 --- a/tensorflow/contrib/cmake/tf_tests.cmake +++ b/tensorflow/contrib/cmake/tf_tests.cmake @@ -210,9 +210,6 @@ if (tensorflow_BUILD_PYTHON_TESTS) "${tensorflow_source_dir}/tensorflow/contrib/learn/python/learn/learn_io/graph_io_test.py" # Test is flaky on Windows GPU builds (b/38283730). "${tensorflow_source_dir}/tensorflow/contrib/factorization/python/ops/gmm_test.py" - # Disable following manual tag in BUILD. - "${tensorflow_source_dir}/tensorflow/python/keras/_impl/keras/layers/convolutional_test.py" - ) if (WIN32) set(tf_test_src_py_exclude diff --git a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py index 75482f67da..5abb38c2d2 100644 --- a/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py +++ b/tensorflow/contrib/data/python/kernel_tests/batch_dataset_op_test.py @@ -413,20 +413,6 @@ class BatchDatasetTest(test.TestCase): def testMapAndBatchPartialBatchDropRemainder(self): return self._testMapAndBatchPartialBatchHelper(drop_remainder=True) - def testMapAndBatchYieldsPartialBatch(self): - iterator = (dataset_ops.Dataset.range(10) - .apply(batching.map_and_batch( - lambda x: array_ops.reshape(x * x, [1]), 4)) - .make_one_shot_iterator()) - self.assertEqual([None, 1], iterator.output_shapes.as_list()) - next_element = iterator.get_next() - with self.test_session() as sess: - self.assertAllEqual([[0], [1], [4], [9]], sess.run(next_element)) - self.assertAllEqual([[16], [25], [36], [49]], sess.run(next_element)) - self.assertAllEqual([[64], [81]], sess.run(next_element)) - with self.assertRaises(errors.OutOfRangeError): - sess.run(next_element) - def testMapAndBatchSparse(self): def _sparse(i): diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD index 80176397c0..4fba014d6f 100644 --- a/tensorflow/contrib/eager/python/BUILD +++ b/tensorflow/contrib/eager/python/BUILD @@ -270,11 +270,7 @@ cuda_py_test( "//tensorflow/python/eager:test", "//tensorflow/python/keras", ], - tags = [ - "no_oss", # b/74395663 - "no_windows", # TODO: needs investigation on Windows - "notsan", - ], + tags = ["notsan"], ) filegroup( diff --git a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py index 9adf47d505..9261823d77 100644 --- a/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py +++ b/tensorflow/contrib/eager/python/examples/spinn/spinn_test.py @@ -418,6 +418,7 @@ class SpinnTest(test_util.TensorFlowTestCase): if event.summary.value and event.summary.value[0].tag == "train/loss"] self.assertEqual(config.epochs, len(train_losses)) + self.assertLess(train_losses[-1], train_losses[0]) # 5. Verify that checkpoints exist and contains all the expected variables. self.assertTrue(glob.glob(os.path.join(config.logdir, "ckpt*"))) diff --git a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py index fa2697800e..e0fae2c992 100644 --- a/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py +++ b/tensorflow/contrib/estimator/python/estimator/replicate_model_fn.py @@ -136,7 +136,7 @@ def replicate_model_fn(model_fn, the train_op argument of `EstimatorSpec`. loss_reduction: controls whether losses are summed or averaged. devices: Optional list of devices to replicate the model across. This - argument can be used to replicate only on the subset of available GPUs. + argument can be used to replice only on the subset of available GPUs. If `None`, then all available GPUs are going to be used for replication. If no GPUs are available, then the model is going to be placed on the CPU. diff --git a/tensorflow/contrib/factorization/kernels/clustering_ops.cc b/tensorflow/contrib/factorization/kernels/clustering_ops.cc index 2a6c97e8b9..dd61f59585 100644 --- a/tensorflow/contrib/factorization/kernels/clustering_ops.cc +++ b/tensorflow/contrib/factorization/kernels/clustering_ops.cc @@ -353,7 +353,7 @@ class NearestNeighborsOp : public OpKernel { auto worker_threads = *(context->device()->tensorflow_cpu_worker_threads()); const int64 num_threads = worker_threads.num_threads; // This kernel might be configured to use fewer than the total number of - // available CPUs on the host machine. To avoid destructive interference + // available CPUs on the host machine. To avoid descructive interference // with other jobs running on the host machine, we must only use a fraction // of total available L3 cache. Unfortunately, we cannot query the host // machine to get the number of physical CPUs. So, we use a fixed per-CPU diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops.py b/tensorflow/contrib/factorization/python/ops/factorization_ops.py index 8e0ed1d80e..054888e734 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops.py @@ -106,7 +106,7 @@ class WALSModel(object): # the prep_gramian_op for row(column) can be run. worker_init_op = model.worker_init - # To be run once per integration sweep before the row(column) update + # To be run once per interation sweep before the row(column) update # initialize ops can be run. Note that in the distributed training # situations, this should only be run by the chief trainer. All other # trainers need to block until this is done. @@ -118,9 +118,9 @@ class WALSModel(object): init_row_update_op = model.initialize_row_update_op init_col_update_op = model.initialize_col_update_op - # Ops to update row(column). This can either take the entire sparse - # tensor or slices of sparse tensor. For distributed trainer, each - # trainer handles just part of the matrix. + # Ops to upate row(column). This can either take the entire sparse tensor + # or slices of sparse tensor. For distributed trainer, each trainer + # handles just part of the matrix. _, row_update_op, unreg_row_loss, row_reg, _ = model.update_row_factors( sp_input=matrix_slices_from_queue_for_worker_shard) row_loss = unreg_row_loss + row_reg @@ -220,7 +220,7 @@ class WALSModel(object): in the form of [[w_0, w_1, ...], [w_k, ... ], [...]], with the number of inner lists matching the number of row factor shards and the elements in each inner list are the weights for the rows of the corresponding row - factor shard. In this case, w_ij = unobserved_weight + + factor shard. In this case, w_ij = unonbserved_weight + row_weights[i] * col_weights[j]. - If this is a single non-negative real number, this value is used for all row weights and w_ij = unobserved_weight + row_weights * @@ -435,7 +435,7 @@ class WALSModel(object): gramian: Variable storing the gramian calculated from the factors. Returns: - A op that updates the gramian with the calculated value from the factors. + A op that updates the gramian with the calcuated value from the factors. """ partial_gramians = [] for f in factors: @@ -564,7 +564,7 @@ class WALSModel(object): Note that specifically this initializes the cache of the row and column weights on workers when `use_factors_weights_cache` is True. In this case, - if these weights are being calculated and reset after the object is created, + if these weights are being calcualted and reset after the object is created, it is important to ensure this ops is run afterwards so the cache reflects the correct values. """ diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py index bb5140aeb3..c813733915 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops_test.py @@ -210,7 +210,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reproduce the same row factors in the model as the + # This is expected to reprodue the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -283,8 +283,8 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 3 column feature vectors. - # This is expected to reproduce the same column factors in the model as - # the weights and feature vectors are identical to that used in model + # This is expected to reprodue the same column factors in the model as the + # weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( sp_input=sp_feeder, @@ -385,7 +385,7 @@ class WalsModelTest(test.TestCase): # Test row projection. # Using the specified projection weights for the 2 row feature vectors. - # This is expected to reproduce the same row factors in the model as the + # This is expected to reprodue the same row factors in the model as the # weights and feature vectors are identical to that used in model # training. projected_rows = wals_model.project_row_factors( @@ -462,8 +462,8 @@ class WalsModelTest(test.TestCase): # Test column projection. # Using the specified projection weights for the 2 column feature vectors. - # This is expected to reproduce the same column factors in the model as - # the weights and feature vectors are identical to that used in model + # This is expected to reprodue the same column factors in the model as the + # weights and feature vectors are identical to that used in model # training. projected_cols = wals_model.project_col_factors( sp_input=sp_feeder, diff --git a/tensorflow/contrib/factorization/python/ops/gmm_ops.py b/tensorflow/contrib/factorization/python/ops/gmm_ops.py index 14d4c733e3..98d6434f47 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_ops.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_ops.py @@ -280,7 +280,7 @@ class GmmAlgorithm(object): self._define_score_samples() def _define_full_covariance_probs(self, shard_id, shard): - """Defines the full covariance probabilities per example in a class. + """Defines the full covariance probabilties per example in a class. Updates a matrix with dimension num_examples X num_classes. @@ -344,7 +344,7 @@ class GmmAlgorithm(object): def _define_prior_log_prob_operation(self, shard_id): """Computes the prior probability of all samples. - Updates a vector where each item is the prior probability of an + Updates a vector where each item is the prior probabibility of an input example. Args: diff --git a/tensorflow/contrib/factorization/python/ops/gmm_test.py b/tensorflow/contrib/factorization/python/ops/gmm_test.py index 4fc9c96e9d..00a4734eb6 100644 --- a/tensorflow/contrib/factorization/python/ops/gmm_test.py +++ b/tensorflow/contrib/factorization/python/ops/gmm_test.py @@ -210,7 +210,7 @@ class GMMTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependent on inputs, if input + # Note that since cluster initialization is dependendent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/kmeans_test.py b/tensorflow/contrib/factorization/python/ops/kmeans_test.py index 88eb9cf692..0103cc4439 100644 --- a/tensorflow/contrib/factorization/python/ops/kmeans_test.py +++ b/tensorflow/contrib/factorization/python/ops/kmeans_test.py @@ -413,7 +413,7 @@ class KMeansCosineDistanceTest(KMeansTestBase): self.assertAllClose(score, self.true_score, atol=1e-2) def test_predict_kmeans_plus_plus(self): - # Most points are concentrated near one center. KMeans++ is likely to find + # Most points are concetrated near one center. KMeans++ is likely to find # the less populated centers. points = np.array( [[2.5, 3.5], [2.5, 3.5], [-2, 3], [-2, 3], [-3, -3], [-3.1, -3.2], @@ -604,7 +604,7 @@ class KMeansTestQueues(test.TestCase): return _fn # This test makes sure that there are no deadlocks when using a QueueRunner. - # Note that since cluster initialization is dependent on inputs, if input + # Note that since cluster initialization is dependendent on inputs, if input # is generated using a QueueRunner, one has to make sure that these runners # are started before the initialization. def test_queues(self): diff --git a/tensorflow/contrib/factorization/python/ops/wals.py b/tensorflow/contrib/factorization/python/ops/wals.py index 62db3bb4c4..4fe22ea26e 100644 --- a/tensorflow/contrib/factorization/python/ops/wals.py +++ b/tensorflow/contrib/factorization/python/ops/wals.py @@ -235,7 +235,7 @@ def _wals_factorization_model_function(features, labels, mode, params): num_items: An integer, the total number of items of this axis. update_fn: A function that takes one argument (`sp_input`), and that returns a tuple of - * new_factors: A float Tensor of the factor values after update. + * new_factors: A flot Tensor of the factor values after update. * update_op: a TensorFlow op which updates the factors. * loss: A float Tensor, the unregularized loss. * reg_loss: A float Tensor, the regularization loss. diff --git a/tensorflow/contrib/learn/BUILD b/tensorflow/contrib/learn/BUILD index 16f80a876f..9c59150580 100644 --- a/tensorflow/contrib/learn/BUILD +++ b/tensorflow/contrib/learn/BUILD @@ -226,7 +226,6 @@ py_test( size = "small", srcs = ["python/learn/monitors_test.py"], srcs_version = "PY2AND3", - tags = ["no_pip_gpu"], # b/74437598 deps = [ ":learn", "//tensorflow/contrib/framework:framework_py", diff --git a/tensorflow/contrib/learn/python/learn/estimators/linear.py b/tensorflow/contrib/learn/python/learn/estimators/linear.py index 70b70af98c..64d7ecc68e 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/linear.py +++ b/tensorflow/contrib/learn/python/learn/estimators/linear.py @@ -243,8 +243,8 @@ def sdca_model_fn(features, labels, mode, params): parent_scope = "linear" - with variable_scope.variable_scope( - values=features.values(), name_or_scope=parent_scope) as scope: + with variable_scope.variable_op_scope( + features.values(), parent_scope) as scope: features = features.copy() features.update(layers.transform_features(features, feature_columns)) logits, columns_to_variables, bias = ( diff --git a/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py b/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py index d4e54c82f9..05794a42c5 100644 --- a/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py +++ b/tensorflow/contrib/linear_optimizer/python/sdca_estimator.py @@ -140,8 +140,8 @@ def sdca_model_fn(features, labels, mode, params, config=None): parent_scope = "linear" - with variable_scope.variable_scope( - values=features.values(), name_or_scope=parent_scope) as scope: + with variable_scope.variable_op_scope(features.values(), + parent_scope) as scope: features = features.copy() features.update(layers.transform_features(features, feature_columns)) logits, columns_to_variables, bias = ( diff --git a/tensorflow/contrib/lite/README.md b/tensorflow/contrib/lite/README.md index c15ae3f233..2680d515eb 100644 --- a/tensorflow/contrib/lite/README.md +++ b/tensorflow/contrib/lite/README.md @@ -126,9 +126,6 @@ The above pre-trained models have been trained on the ImageNet data set, which c The [TensorFlow for Poets](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets/) codelab walks through this process step-by-step. The retraining code supports retraining for both floating point and quantized inference. -# Getting started with RaspberryPi - -Using RaspberryPi can be accomplished by following the [Makefile instructions](g3doc/rpi.md). That will give a you a static library (.a) that you can build your app against. Python bindings will be coming soon as well as a demo app. ### Train a custom model A developer may choose to train a custom model using Tensorflow. TensorFlow documentation has [several tutorials](https://www.tensorflow.org/tutorials/) for building and training models. If the user has written a model using TensorFlow's Slim Framework the first step is to export this to a GraphDef file. This is necessary because Slim does not store the model structure outside the code, so to communicate with other parts of the framework it needs to be exported. Documentation for the export can be found [here](https://github.com/tensorflow/models/tree/master/research/slim#Export). The output of this step will be a .pb file for the custom model. diff --git a/tensorflow/contrib/lite/builtin_ops.h b/tensorflow/contrib/lite/builtin_ops.h index 17b791e4e2..d7993e60cc 100644 --- a/tensorflow/contrib/lite/builtin_ops.h +++ b/tensorflow/contrib/lite/builtin_ops.h @@ -79,7 +79,6 @@ typedef enum { kTfLiteBuiltinBidirectionalSequenceLstm = 52, kTfLiteBuiltinCast = 53, kTfLiteBuiltinPrelu = 54, - kTfLiteBuiltinMaximum = 55, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/contrib/lite/g3doc/models.md b/tensorflow/contrib/lite/g3doc/models.md index 48f43d4fc4..5b393140d6 100644 --- a/tensorflow/contrib/lite/g3doc/models.md +++ b/tensorflow/contrib/lite/g3doc/models.md @@ -1,4 +1,4 @@ -# List of Hosted Models +#List of Hosted Models * [Inception V3 2015](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_2015_2017_11_10.zip) * [Inception V3 Slim 2016](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) diff --git a/tensorflow/contrib/lite/kernels/BUILD b/tensorflow/contrib/lite/kernels/BUILD index c423c00bf5..1450c1e14b 100644 --- a/tensorflow/contrib/lite/kernels/BUILD +++ b/tensorflow/contrib/lite/kernels/BUILD @@ -156,7 +156,6 @@ cc_library( "local_response_norm.cc", "lsh_projection.cc", "lstm.cc", - "maximum.cc", "mean.cc", "mfcc.cc", "mul.cc", @@ -537,18 +536,6 @@ tf_cc_test( ], ) -tf_cc_test( - name = "maximum_test", - size = "small", - srcs = ["maximum_test.cc"], - deps = [ - ":builtin_ops", - "//tensorflow/contrib/lite:framework", - "//tensorflow/contrib/lite/kernels:test_util", - "@com_google_googletest//:gtest", - ], -) - tf_cc_test( name = "mean_test", size = "small", diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h index 3575974ae9..33d60afa26 100644 --- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h @@ -404,7 +404,6 @@ inline void DepthToSpace(const T* input_data, const Dims<4>& input_dims, const int in_d = out_d + ((out_h % block_size) * block_size + out_w % block_size) * output_depth; - const int in_w = out_w / block_size; const int in_h = out_h / block_size; const int in_b = out_b; @@ -3364,30 +3363,6 @@ void TensorFlowMaximum(const T* input1_data, const Dims<4>& input1_dims, } } -template -void TensorFlowMaximum(const T* input1_data, const Dims<4>& input1_dims, - const T* input2_data, const Dims<4>& input2_dims, - T* output_data, const Dims<4>& output_dims) { - NdArrayDesc<4> desc1; - NdArrayDesc<4> desc2; - NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); - - for (int b = 0; b < ArraySize(output_dims, 3); ++b) { - for (int y = 0; y < ArraySize(output_dims, 2); ++y) { - for (int x = 0; x < ArraySize(output_dims, 1); ++x) { - for (int c = 0; c < ArraySize(output_dims, 0); ++c) { - auto out_idx = Offset(output_dims, c, x, y, b); - auto in1_idx = SubscriptToIndex(desc1, c, x, y, b); - auto in2_idx = SubscriptToIndex(desc2, c, x, y, b); - auto in1_val = input1_data[in1_idx]; - auto in2_val = input2_data[in2_idx]; - output_data[out_idx] = in1_val > in2_val ? in1_val : in2_val; - } - } - } - } -} - template void ArgMax(const T3* axis, const T1* input_data, const Dims<4>& input_dims, T2* output_data, const Dims<4>& output_dims) { diff --git a/tensorflow/contrib/lite/kernels/maximum.cc b/tensorflow/contrib/lite/kernels/maximum.cc deleted file mode 100644 index 9fdf2b47ea..0000000000 --- a/tensorflow/contrib/lite/kernels/maximum.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include -#include -#include "tensorflow/contrib/lite/builtin_op_data.h" -#include "tensorflow/contrib/lite/context.h" -#include "tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h" -#include "tensorflow/contrib/lite/kernels/internal/tensor.h" -#include "tensorflow/contrib/lite/kernels/kernel_util.h" -#include "tensorflow/contrib/lite/kernels/op_macros.h" - -namespace tflite { -namespace ops { -namespace builtin { -namespace maximum { - -// This file has a reference implemenation of TFMaximum. -enum KernelType { - kReference, -}; - -constexpr int kInputTensor1 = 0; -constexpr int kInputTensor2 = 1; -constexpr int kOutputTensor = 0; - -struct MaximumContext { - MaximumContext(TfLiteContext* context, TfLiteNode* node) { - input1 = GetInput(context, node, kInputTensor1); - input2 = GetInput(context, node, kInputTensor2); - output = GetOutput(context, node, kOutputTensor); - } - TfLiteTensor* input1; - TfLiteTensor* input2; - TfLiteTensor* output; -}; - -TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { - TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); - TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); - - MaximumContext op_context(context, node); - TF_LITE_ENSURE_EQ(context, op_context.input1->type, op_context.input2->type); - TfLiteIntArray* output_dims = TfLiteIntArrayCopy(op_context.input2->dims); - op_context.output->type = op_context.input2->type; - return context->ResizeTensor(context, op_context.output, output_dims); -} - -template -TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { - MaximumContext op_context(context, node); - -#define TF_LITE_MAXIMUM(kernel_type, data_type) \ - kernel_type::TensorFlowMaximum( \ - GetTensorData(op_context.input1), \ - GetTensorDims(op_context.input1), \ - GetTensorData(op_context.input2), \ - GetTensorDims(op_context.input2), \ - GetTensorData(op_context.output), \ - GetTensorDims(op_context.output)) - - if (kernel_type == kReference) { - switch (op_context.output->type) { - case kTfLiteFloat32: - TF_LITE_MAXIMUM(reference_ops, float); - break; - default: - context->ReportError(context, - "Type %d is currently not supported by Maximum.", - op_context.output->type); - return kTfLiteError; - } - } else { - context->ReportError(context, - "Type %d is currently not supported by Maximum.", - op_context.output->type); - return kTfLiteError; - } -#undef TF_LITE_MAXIMUM - return kTfLiteOk; -} - -} // namespace maximum - -TfLiteRegistration* Register_MAXIMUM_REF() { - static TfLiteRegistration r = {nullptr, nullptr, maximum::Prepare, - maximum::Eval}; - return &r; -} - -TfLiteRegistration* Register_MAXIMUM() { return Register_MAXIMUM_REF(); } - -} // namespace builtin -} // namespace ops -} // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/maximum_test.cc b/tensorflow/contrib/lite/kernels/maximum_test.cc deleted file mode 100644 index b3fd7d4e6f..0000000000 --- a/tensorflow/contrib/lite/kernels/maximum_test.cc +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include -#include "tensorflow/contrib/lite/interpreter.h" -#include "tensorflow/contrib/lite/kernels/register.h" -#include "tensorflow/contrib/lite/kernels/test_util.h" -#include "tensorflow/contrib/lite/model.h" - -namespace tflite { -namespace { - -using ::testing::ElementsAreArray; - -class MaximumOpModel : public SingleOpModel { - public: - MaximumOpModel(const TensorData& input1, const TensorData& input2, - const TensorType& output) { - input1_ = AddInput(input1); - input2_ = AddInput(input2); - output_ = AddOutput(output); - SetBuiltinOp(BuiltinOperator_MAXIMUM, BuiltinOptions_MaximumOptions, - CreateMaximumOptions(builder_).Union()); - BuildInterpreter({GetShape(input1_), GetShape(input2_)}); - } - - template - void SetInput1(std::initializer_list data) { - PopulateTensor(input1_, data); - } - - template - void SetInput2(std::initializer_list data) { - PopulateTensor(input2_, data); - } - - template - std::vector GetOutput() { - return ExtractVector(output_); - } - std::vector GetOutputShape() { return GetTensorShape(output_); } - - protected: - int input1_; - int input2_; - int output_; -}; - -TEST(MaximumOpTest, FloatTest) { - std::initializer_list data1 = {1.0, 0.0, -1.0, 11.0, -2.0, -1.44}; - std::initializer_list data2 = {-1.0, 0.0, 1.0, 12.0, -3.0, -1.43}; - MaximumOpModel m({TensorType_FLOAT32, {3, 1, 2}}, - {TensorType_FLOAT32, {3, 1, 2}}, TensorType_FLOAT32); - m.SetInput1(data1); - m.SetInput2(data2); - m.Invoke(); - EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1, 2})); - EXPECT_THAT( - m.GetOutput(), - ElementsAreArray(ArrayFloatNear({1.0, 0.0, 1.0, 12.0, -2.0, -1.43}))); -} - -} // namespace -} // namespace tflite - -int main(int argc, char** argv) { - ::tflite::LogToStderr(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tensorflow/contrib/lite/kernels/register.cc b/tensorflow/contrib/lite/kernels/register.cc index 0f98154b90..62045f0a4d 100644 --- a/tensorflow/contrib/lite/kernels/register.cc +++ b/tensorflow/contrib/lite/kernels/register.cc @@ -76,7 +76,6 @@ TfLiteRegistration* Register_LOG_SOFTMAX(); TfLiteRegistration* Register_CAST(); TfLiteRegistration* Register_DEQUANTIZE(); TfLiteRegistration* Register_PRELU(); -TfLiteRegistration* Register_MAXIMUM(); BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_RELU, Register_RELU()); @@ -134,7 +133,6 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_CAST, Register_CAST()); AddBuiltin(BuiltinOperator_DEQUANTIZE, Register_DEQUANTIZE()); AddBuiltin(BuiltinOperator_PRELU, Register_PRELU()); - AddBuiltin(BuiltinOperator_MAXIMUM, Register_MAXIMUM()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc index 791d1378f3..b7ccdf070b 100644 --- a/tensorflow/contrib/lite/model.cc +++ b/tensorflow/contrib/lite/model.cc @@ -597,9 +597,6 @@ void* ParseOpData(const Operator* op, BuiltinOperator op_type, builtin_data = reinterpret_cast(params); break; } - case BuiltinOperator_MAXIMUM: { - break; - } case BuiltinOperator_DELEGATE: { // TODO(ycling): Revisit when supporting saving delegated models. error_reporter->Report("DELEGATE op shouldn't exist in model."); diff --git a/tensorflow/contrib/lite/nnapi_delegate.cc b/tensorflow/contrib/lite/nnapi_delegate.cc index decaf9f160..e31b7c03a5 100644 --- a/tensorflow/contrib/lite/nnapi_delegate.cc +++ b/tensorflow/contrib/lite/nnapi_delegate.cc @@ -350,7 +350,6 @@ void AddOpsAndParams(tflite::Interpreter* interpreter, case tflite::BuiltinOperator_DELEGATE: case tflite::BuiltinOperator_CAST: case tflite::BuiltinOperator_PRELU: - case tflite::BuiltinOperator_MAXIMUM: FATAL("Op code %d is currently not delegated to NNAPI", builtin); nn_op_type = -1; // set to invalid break; diff --git a/tensorflow/contrib/lite/python/lite.py b/tensorflow/contrib/lite/python/lite.py index ed6dd036f9..35d224924e 100644 --- a/tensorflow/contrib/lite/python/lite.py +++ b/tensorflow/contrib/lite/python/lite.py @@ -25,9 +25,9 @@ EXPERIMENTAL: APIs here are unstable and likely to change without notice. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os as _os -import subprocess as _subprocess -import tempfile as _tempfile +import os +import subprocess +import tempfile # pylint: disable=unused-import from tensorflow.contrib.lite.python.op_hint import convert_op_hints_to_stubs @@ -74,7 +74,7 @@ else: _toco_from_proto_bin = _resource_loader.get_path_to_datafile( "../toco/python/toco_from_protos") -if _toco_from_proto_bin and not _os.path.exists(_toco_from_proto_bin): +if _toco_from_proto_bin and not os.path.exists(_toco_from_proto_bin): _toco_from_proto_bin = "toco_from_protos" @@ -102,10 +102,10 @@ def toco_convert_protos(model_flags_str, toco_flags_str, input_data_str): return _toco_python.TocoConvert( model_flags_str, toco_flags_str, input_data_str) - with _tempfile.NamedTemporaryFile() as fp_toco, \ - _tempfile.NamedTemporaryFile() as fp_model, \ - _tempfile.NamedTemporaryFile() as fp_input, \ - _tempfile.NamedTemporaryFile() as fp_output: + with tempfile.NamedTemporaryFile() as fp_toco, \ + tempfile.NamedTemporaryFile() as fp_model, \ + tempfile.NamedTemporaryFile() as fp_input, \ + tempfile.NamedTemporaryFile() as fp_output: fp_model.write(model_flags_str) fp_toco.write(toco_flags_str) fp_input.write(input_data_str) @@ -118,11 +118,11 @@ def toco_convert_protos(model_flags_str, toco_flags_str, input_data_str): fp_output.name ] cmdline = " ".join(cmd) - proc = _subprocess.Popen( + proc = subprocess.Popen( cmdline, shell=True, - stdout=_subprocess.PIPE, - stderr=_subprocess.STDOUT, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, close_fds=True) stdout, stderr = proc.communicate() exitcode = proc.returncode diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs index 7d2e00fe32..e1075971e9 100644 --- a/tensorflow/contrib/lite/schema/schema.fbs +++ b/tensorflow/contrib/lite/schema/schema.fbs @@ -131,7 +131,6 @@ enum BuiltinOperator : byte { BIDIRECTIONAL_SEQUENCE_LSTM = 52, CAST = 53, PRELU = 54, - MAXIMUM = 55, } // Options for the builtin operators. @@ -174,7 +173,6 @@ union BuiltinOptions { LogSoftmaxOptions, CastOptions, DequantizeOptions, - MaximumOptions, } enum Padding : byte { SAME, VALID } @@ -386,9 +384,6 @@ table CastOptions { table DequantizeOptions { } -table MaximumOptions { -} - // An OperatorCode can be an enum value (BuiltinOperator) if the operator is a // builtin, or a string if the operator is custom. table OperatorCode { diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h index 66a97a1460..86daeaf5cc 100755 --- a/tensorflow/contrib/lite/schema/schema_generated.h +++ b/tensorflow/contrib/lite/schema/schema_generated.h @@ -145,9 +145,6 @@ struct CastOptionsT; struct DequantizeOptions; struct DequantizeOptionsT; -struct MaximumOptions; -struct MaximumOptionsT; - struct OperatorCode; struct OperatorCodeT; @@ -258,12 +255,11 @@ enum BuiltinOperator { BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM = 52, BuiltinOperator_CAST = 53, BuiltinOperator_PRELU = 54, - BuiltinOperator_MAXIMUM = 55, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_MAXIMUM + BuiltinOperator_MAX = BuiltinOperator_PRELU }; -inline BuiltinOperator (&EnumValuesBuiltinOperator())[54] { +inline BuiltinOperator (&EnumValuesBuiltinOperator())[53] { static BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -317,8 +313,7 @@ inline BuiltinOperator (&EnumValuesBuiltinOperator())[54] { BuiltinOperator_DELEGATE, BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM, BuiltinOperator_CAST, - BuiltinOperator_PRELU, - BuiltinOperator_MAXIMUM + BuiltinOperator_PRELU }; return values; } @@ -380,7 +375,6 @@ inline const char **EnumNamesBuiltinOperator() { "BIDIRECTIONAL_SEQUENCE_LSTM", "CAST", "PRELU", - "MAXIMUM", nullptr }; return names; @@ -431,12 +425,11 @@ enum BuiltinOptions { BuiltinOptions_LogSoftmaxOptions = 36, BuiltinOptions_CastOptions = 37, BuiltinOptions_DequantizeOptions = 38, - BuiltinOptions_MaximumOptions = 39, BuiltinOptions_MIN = BuiltinOptions_NONE, - BuiltinOptions_MAX = BuiltinOptions_MaximumOptions + BuiltinOptions_MAX = BuiltinOptions_DequantizeOptions }; -inline BuiltinOptions (&EnumValuesBuiltinOptions())[40] { +inline BuiltinOptions (&EnumValuesBuiltinOptions())[39] { static BuiltinOptions values[] = { BuiltinOptions_NONE, BuiltinOptions_Conv2DOptions, @@ -476,8 +469,7 @@ inline BuiltinOptions (&EnumValuesBuiltinOptions())[40] { BuiltinOptions_SplitOptions, BuiltinOptions_LogSoftmaxOptions, BuiltinOptions_CastOptions, - BuiltinOptions_DequantizeOptions, - BuiltinOptions_MaximumOptions + BuiltinOptions_DequantizeOptions }; return values; } @@ -523,7 +515,6 @@ inline const char **EnumNamesBuiltinOptions() { "LogSoftmaxOptions", "CastOptions", "DequantizeOptions", - "MaximumOptions", nullptr }; return names; @@ -690,10 +681,6 @@ template<> struct BuiltinOptionsTraits { static const BuiltinOptions enum_value = BuiltinOptions_DequantizeOptions; }; -template<> struct BuiltinOptionsTraits { - static const BuiltinOptions enum_value = BuiltinOptions_MaximumOptions; -}; - struct BuiltinOptionsUnion { BuiltinOptions type; void *value; @@ -1029,14 +1016,6 @@ struct BuiltinOptionsUnion { return type == BuiltinOptions_DequantizeOptions ? reinterpret_cast(value) : nullptr; } - MaximumOptionsT *AsMaximumOptions() { - return type == BuiltinOptions_MaximumOptions ? - reinterpret_cast(value) : nullptr; - } - const MaximumOptionsT *AsMaximumOptions() const { - return type == BuiltinOptions_MaximumOptions ? - reinterpret_cast(value) : nullptr; - } }; bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *obj, BuiltinOptions type); @@ -3780,46 +3759,6 @@ inline flatbuffers::Offset CreateDequantizeOptions( flatbuffers::Offset CreateDequantizeOptions(flatbuffers::FlatBufferBuilder &_fbb, const DequantizeOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); -struct MaximumOptionsT : public flatbuffers::NativeTable { - typedef MaximumOptions TableType; - MaximumOptionsT() { - } -}; - -struct MaximumOptions FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { - typedef MaximumOptionsT NativeTableType; - bool Verify(flatbuffers::Verifier &verifier) const { - return VerifyTableStart(verifier) && - verifier.EndTable(); - } - MaximumOptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; - void UnPackTo(MaximumOptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; - static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); -}; - -struct MaximumOptionsBuilder { - flatbuffers::FlatBufferBuilder &fbb_; - flatbuffers::uoffset_t start_; - explicit MaximumOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) - : fbb_(_fbb) { - start_ = fbb_.StartTable(); - } - MaximumOptionsBuilder &operator=(const MaximumOptionsBuilder &); - flatbuffers::Offset Finish() { - const auto end = fbb_.EndTable(start_); - auto o = flatbuffers::Offset(end); - return o; - } -}; - -inline flatbuffers::Offset CreateMaximumOptions( - flatbuffers::FlatBufferBuilder &_fbb) { - MaximumOptionsBuilder builder_(_fbb); - return builder_.Finish(); -} - -flatbuffers::Offset CreateMaximumOptions(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); - struct OperatorCodeT : public flatbuffers::NativeTable { typedef OperatorCode TableType; BuiltinOperator builtin_code; @@ -4051,9 +3990,6 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const DequantizeOptions *builtin_options_as_DequantizeOptions() const { return builtin_options_type() == BuiltinOptions_DequantizeOptions ? static_cast(builtin_options()) : nullptr; } - const MaximumOptions *builtin_options_as_MaximumOptions() const { - return builtin_options_type() == BuiltinOptions_MaximumOptions ? static_cast(builtin_options()) : nullptr; - } const flatbuffers::Vector *custom_options() const { return GetPointer *>(VT_CUSTOM_OPTIONS); } @@ -4232,10 +4168,6 @@ template<> inline const DequantizeOptions *Operator::builtin_options_as inline const MaximumOptions *Operator::builtin_options_as() const { - return builtin_options_as_MaximumOptions(); -} - struct OperatorBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; @@ -5764,29 +5696,6 @@ inline flatbuffers::Offset CreateDequantizeOptions(flatbuffer _fbb); } -inline MaximumOptionsT *MaximumOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const { - auto _o = new MaximumOptionsT(); - UnPackTo(_o, _resolver); - return _o; -} - -inline void MaximumOptions::UnPackTo(MaximumOptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const { - (void)_o; - (void)_resolver; -} - -inline flatbuffers::Offset MaximumOptions::Pack(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { - return CreateMaximumOptions(_fbb, _o, _rehasher); -} - -inline flatbuffers::Offset CreateMaximumOptions(flatbuffers::FlatBufferBuilder &_fbb, const MaximumOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) { - (void)_rehasher; - (void)_o; - struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const MaximumOptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; - return tflite::CreateMaximumOptions( - _fbb); -} - inline OperatorCodeT *OperatorCode::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = new OperatorCodeT(); UnPackTo(_o, _resolver); @@ -6119,10 +6028,6 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case BuiltinOptions_MaximumOptions: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } default: return false; } } @@ -6293,10 +6198,6 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); } - case BuiltinOptions_MaximumOptions: { - auto ptr = reinterpret_cast(obj); - return ptr->UnPack(resolver); - } default: return nullptr; } } @@ -6455,10 +6356,6 @@ inline flatbuffers::Offset BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff auto ptr = reinterpret_cast(value); return CreateDequantizeOptions(_fbb, ptr, _rehasher).Union(); } - case BuiltinOptions_MaximumOptions: { - auto ptr = reinterpret_cast(value); - return CreateMaximumOptions(_fbb, ptr, _rehasher).Union(); - } default: return 0; } } @@ -6617,10 +6514,6 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL value = new DequantizeOptionsT(*reinterpret_cast(u.value)); break; } - case BuiltinOptions_MaximumOptions: { - value = new MaximumOptionsT(*reinterpret_cast(u.value)); - break; - } default: break; } @@ -6818,11 +6711,6 @@ inline void BuiltinOptionsUnion::Reset() { delete ptr; break; } - case BuiltinOptions_MaximumOptions: { - auto ptr = reinterpret_cast(value); - delete ptr; - break; - } default: break; } value = nullptr; diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index 12b7b3c350..555ea90034 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -36,7 +36,6 @@ gen_zipped_test_files( "local_response_norm.zip", "log_softmax.zip", "max_pool.zip", - "maximum.zip", "mean.zip", "mul.zip", "pad.zip", diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py index 8045052452..cb5c500136 100644 --- a/tensorflow/contrib/lite/testing/generate_examples.py +++ b/tensorflow/contrib/lite/testing/generate_examples.py @@ -862,41 +862,6 @@ def make_log_softmax_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) -def make_maximum_tests(zip_path): - """Make a set of tests to do maximum.""" - - test_parameters = [{ - "input_dtype": [tf.float32], - "input_shape_1": [[3], [1, 100], [4, 2, 3], [5, 224, 224, 3]], - "input_shape_2": [[3], [1, 100], [4, 2, 3], [5, 224, 224, 3]], - }] - - def build_graph(parameters): - """Build the maximum op testing graph.""" - input_tensor_1 = tf.placeholder( - dtype=parameters["input_dtype"], - name="input_1", - shape=parameters["input_shape_1"]) - input_tensor_2 = tf.placeholder( - dtype=parameters["input_dtype"], - name="input_2", - shape=parameters["input_shape_2"]) - - out = tf.maximum(input_tensor_1, input_tensor_2) - return [input_tensor_1, input_tensor_2], [out] - - def build_inputs(parameters, sess, inputs, outputs): - values = [ - create_tensor_data(parameters["input_dtype"], - parameters["input_shape_1"]), - create_tensor_data(parameters["input_dtype"], - parameters["input_shape_2"]) - ] - return values, sess.run(outputs, feed_dict=dict(zip(inputs, values))) - - make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) - - def make_binary_op_tests_func(binary_operator): """Return a function that does a test on a binary operator.""" return lambda zip_path: make_binary_op_tests(zip_path, binary_operator) @@ -2012,7 +1977,6 @@ def main(unused_args): "exp.zip": make_exp_tests, "log_softmax.zip": make_log_softmax_tests, "lstm.zip": make_lstm_tests, - "maximum.zip": make_maximum_tests, } out = FLAGS.zip_to_output bin_path = FLAGS.toco diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc index 6697b86e79..a4a7283508 100644 --- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc @@ -253,7 +253,6 @@ INSTANTIATE_TESTS(l2_pool) INSTANTIATE_TESTS(l2norm) INSTANTIATE_TESTS(local_response_norm) INSTANTIATE_TESTS(log_softmax) -INSTANTIATE_TESTS(maximum) INSTANTIATE_TESTS(max_pool) INSTANTIATE_TESTS(mean) INSTANTIATE_TESTS(mul) diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc index 0989bfe5a3..f23249cfa1 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator.cc @@ -863,8 +863,6 @@ std::vector> BuildOperatorList() { ops.emplace_back(new SimpleOperator("EXP", OperatorType::kExp)); ops.emplace_back(new SimpleOperator( "LOG_SOFTMAX", OperatorType::kLogSoftmax)); - ops.emplace_back(new SimpleOperator( - "MAXIMUM", OperatorType::kTensorFlowMaximum)); return ops; } diff --git a/tensorflow/contrib/lite/toco/tflite/operator_test.cc b/tensorflow/contrib/lite/toco/tflite/operator_test.cc index f7a213ecfc..9c19f8d464 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator_test.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator_test.cc @@ -109,8 +109,6 @@ TEST_F(OperatorTest, SimpleOperators) { CheckSimpleOperator("EXP", OperatorType::kExp); CheckSimpleOperator("LOG_SOFTMAX", OperatorType::kLogSoftmax); - CheckSimpleOperator( - "MAXIMUM", OperatorType::kTensorFlowMaximum); } TEST_F(OperatorTest, BuiltinAdd) { diff --git a/tensorflow/contrib/lookup/lookup_ops.py b/tensorflow/contrib/lookup/lookup_ops.py index a03e731be3..a57a1e5421 100644 --- a/tensorflow/contrib/lookup/lookup_ops.py +++ b/tensorflow/contrib/lookup/lookup_ops.py @@ -494,7 +494,7 @@ class MutableDenseHashTable(LookupInterface): value_dtype=tf.int64, default_value=-1, empty_key=0) - sess.run(table.insert(keys, values)) + table.insert(keys, values) out = table.lookup(query_keys) print(out.eval()) ``` diff --git a/tensorflow/contrib/makefile/download_dependencies.sh b/tensorflow/contrib/makefile/download_dependencies.sh index 8b415e6527..4ae18b2cef 100755 --- a/tensorflow/contrib/makefile/download_dependencies.sh +++ b/tensorflow/contrib/makefile/download_dependencies.sh @@ -34,7 +34,7 @@ PROTOBUF_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/protobuf/. RE2_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/re2/.*tar\.gz' "${BZL_FILE_PATH}" | head -n1)" FFT2D_URL="$(grep -o 'http.*fft\.tgz' "${BZL_FILE_PATH}" | grep -v mirror.bazel | head -n1)" ABSL_URL="$(grep -o 'https://github.com/abseil/abseil-cpp/.*tar.gz' "${BZL_FILE_PATH}" | head -n1)" -CUB_URL="$(grep -o 'https.*cub/archive.*zip' "${BZL_FILE_PATH}" | grep -v mirror.bazel | head -n1)" +CUB_URL="$(grep -o 'https.*cub/archive.*zip' "${BZL_FILE_PATH}" | grep -v bazel-mirror | head -n1)" # TODO(petewarden): Some new code in Eigen triggers a clang bug with iOS arm64, # so work around it by patching the source. diff --git a/tensorflow/contrib/makefile/tf_op_files.txt b/tensorflow/contrib/makefile/tf_op_files.txt index 7a7683c953..5a812af4e9 100644 --- a/tensorflow/contrib/makefile/tf_op_files.txt +++ b/tensorflow/contrib/makefile/tf_op_files.txt @@ -258,7 +258,6 @@ tensorflow/core/kernels/requantize.cc tensorflow/core/kernels/remote_fused_graph_execute_op.cc tensorflow/core/kernels/remote_fused_graph_execute_utils.cc tensorflow/core/kernels/batch_matmul_op_real.cc -tensorflow/core/kernels/random_op.cc tensorflow/core/ops/training_ops.cc tensorflow/core/ops/string_ops.cc tensorflow/core/ops/state_ops.cc diff --git a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc index a9a32b7b25..dfa12e873a 100644 --- a/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc +++ b/tensorflow/contrib/seq2seq/kernels/beam_search_ops.cc @@ -74,7 +74,7 @@ class GatherTreeOp : public OpKernel { ctx, step_ids_shape.dim_size(1) == max_sequence_lengths.shape().dim_size(0), errors::InvalidArgument("batch size dimensions step_ids.shape[1] and " - "max_sequence_lengths.shape[0] must match. " + "max_seqeuence_lengths.shape[0] must match. " "but shapes are: ", step_ids_shape.DebugString(), " and ", max_sequence_lengths.shape().DebugString())); diff --git a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py index be53779826..9ff8a343f1 100644 --- a/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py +++ b/tensorflow/contrib/seq2seq/python/ops/attention_wrapper.py @@ -736,7 +736,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): """Base attention mechanism for monotonic attention. Simply overrides the initial_alignments function to provide a dirac - distribution, which is needed in order for the monotonic attention + distribution,which is needed in order for the monotonic attention distributions to have the correct behavior. """ @@ -763,7 +763,7 @@ class _BaseMonotonicAttentionMechanism(_BaseAttentionMechanism): class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Bahadanau-style energy function. - This type of attention enforces a monotonic constraint on the attention + This type of attention encorces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -867,7 +867,7 @@ class BahdanauMonotonicAttention(_BaseMonotonicAttentionMechanism): class LuongMonotonicAttention(_BaseMonotonicAttentionMechanism): """Monotonic attention mechanism with Luong-style energy function. - This type of attention enforces a monotonic constraint on the attention + This type of attention encorces a monotonic constraint on the attention distributions; that is once the model attends to a given point in the memory it can't attend to any prior points at subsequence output timesteps. It achieves this by using the _monotonic_probability_fn instead of softmax to @@ -1133,7 +1133,7 @@ class AttentionWrapper(rnn_cell_impl.RNNCell): output_attention: Python bool. If `True` (default), the output at each time step is the attention value. This is the behavior of Luong-style attention mechanisms. If `False`, the output at each time step is - the output of `cell`. This is the behavior of Bhadanau-style + the output of `cell`. This is the beahvior of Bhadanau-style attention mechanisms. In both cases, the `attention` tensor is propagated to the next time step via the state and is used there. This flag only controls whether the attention mechanism is propagated diff --git a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py index 184144f64a..a26107b0d7 100644 --- a/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py +++ b/tensorflow/contrib/seq2seq/python/ops/beam_search_decoder.py @@ -821,9 +821,9 @@ def _get_scores(log_probs, sequence_lengths, length_penalty_weight): Returns: The scores normalized by the length_penalty. """ - length_penalty_ = _length_penalty( + length_penality_ = _length_penalty( sequence_lengths=sequence_lengths, penalty_factor=length_penalty_weight) - return log_probs / length_penalty_ + return log_probs / length_penality_ def _length_penalty(sequence_lengths, penalty_factor): @@ -860,7 +860,7 @@ def _mask_probs(probs, eos_token, finished): unfinished beams remain unchanged. Args: - probs: Log probabilities of shape `[batch_size, beam_width, vocab_size]` + probs: Log probabiltiies of shape `[batch_size, beam_width, vocab_size]` eos_token: An int32 id corresponding to the EOS token to allocate probability to. finished: A boolean tensor of shape `[batch_size, beam_width]` that diff --git a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py index 99ad487630..b3343aef47 100644 --- a/tensorflow/contrib/slim/python/slim/data/parallel_reader.py +++ b/tensorflow/contrib/slim/python/slim/data/parallel_reader.py @@ -115,8 +115,8 @@ class ParallelReader(io_ops.ReaderBase): reader needs to start reading from a new file since it has finished with the previous file). - A queue runner for enqueuing in the `common_queue` is automatically added - to the TF QueueRunners collection. + A queue runner for enqueing in the `common_queue` is automatically added to + the TF QueueRunners collection. Args: queue: A Queue or a mutable string Tensor representing a handle diff --git a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py index 62bd200361..37e9c4754c 100644 --- a/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py +++ b/tensorflow/contrib/slim/python/slim/data/prefetch_queue.py @@ -36,9 +36,9 @@ def prefetch_queue(tensors, dynamic_pad=False, shared_name=None, name=None): - """Creates a queue to prefetch tensors from `tensors`. + """Creates a queue to prefetech tensors from `tensors`. - A queue runner for enqueuing tensors into the prefetch_queue is automatically + A queue runner for enqueing tensors into the prefetch_queue is automatically added to the TF QueueRunners collection. Example: diff --git a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py index f2d31dc8db..b3b61e1dfe 100644 --- a/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py +++ b/tensorflow/contrib/slim/python/slim/data/tfexample_decoder.py @@ -124,7 +124,7 @@ class BoundingBox(ItemHandler): super(BoundingBox, self).__init__(self._full_keys) def tensors_to_item(self, keys_to_tensors): - """Maps the given dictionary of tensors to a concatenated list of bboxes. + """Maps the given dictionary of tensors to a contatenated list of bboxes. Args: keys_to_tensors: a mapping of TF-Example keys to parsed tensors. diff --git a/tensorflow/contrib/tensorrt/README.md b/tensorflow/contrib/tensorrt/README.md index 6eafc1754c..461e627e99 100644 --- a/tensorflow/contrib/tensorrt/README.md +++ b/tensorflow/contrib/tensorrt/README.md @@ -1,15 +1,15 @@ -# Using TensorRT in TensorFlow - +Using TensorRT in TensorFlow +============================ This module provides necessary bindings and introduces TRT_engine_op operator that wraps a subgraph in TensorRT. This is still a work in progress but should be useable with most common graphs. -## Compilation - +Compilation +----------- In order to compile the module, you need to have a local TensorRT -installation ( libnvinfer.so and respective include files ). During the +installation (libnvinfer.so and respective include files). During the configuration step, TensorRT should be enabled and installation path should be set. If installed through package managers (deb,rpm), configure script should find the necessary components from the system @@ -22,38 +22,4 @@ bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/ ``` After the installation of tensorflow package, TensorRT transformation -will be available. An example use can be found in test/test_tftrt.py script - -## Installing TensorRT 3.0.4 - -In order to make use of TensorRT integration, you will need a local installation of TensorRT 3.0.4 from the [NVIDIA Developer website](https://developer.nvidia.com/tensorrt). Due to compiler compatibility, you will need to download and install the TensorRT 3.0.4 tarball for _Ubuntu 14.04_, i.e., **_TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz_**, even if you are using Ubuntu 16.04 or later. - -### Preparing TensorRT installation - -Once you have downloaded TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz, you will need to unpack it to an installation directory, which will be referred to as . Please replace with the full path of actual installation directory you choose in commands below. - -```shell -cd && tar -zxf /path/to/TensorRT-3.0.4.Ubuntu-14.04.5.x86_64.cuda-9.0.cudnn7.0-tar.gz -``` - -After unpacking the binaries, you have several options to use them: - -#### To run TensorFlow as a user without superuser privileges - -For a regular user without any sudo rights, you should add TensorRT to your `$LD_LIBRARY_PATH`: - - ```shell - export LD_LIBRARY_PATH=/TensorRT-3.0.4/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - ``` - -Then you are ready to use TensorFlow-TensorRT integration. `$LD_LIBRARY_PATH` must contain the path to TensorRT installation for TensorFlow-TensorRT integration to work. If you are using a VirtualEnv-like setup, you can add the command above to your `bin/activate` script or to your `.bashrc` script. - -#### To run TensorFlow as a superuser - - When running as a superuser, such as in a container or via sudo, the `$LD_LIBRARY_PATH` approach above may not work. The following is preferred when the user has superuser privileges: - - ```shell - echo "/TensorRT-3.0.4/lib" | sudo tee /etc/ld.so.conf.d/tensorrt304.conf && sudo ldconfig - ``` - - Please ensure that any existing deb package installation of TensorRT is removed before following these instructions to avoid package conflicts. \ No newline at end of file +will be available. An example use can be found in test/test_tftrt.py directory diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index ff8cc6374d..eea8c8efa2 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -49,13 +49,12 @@ namespace tensorrt { namespace convert { namespace { -bool IsTensorRTCandidate(const tensorflow::Node* node) { +bool IsTensorRTCandidate(const tensorflow::NodeDef& node_def) { // LINT.IfChange // TODO(jie): Segmentation shouldn't associated with op name. // Split it into a registration for each kernel. static const std::set candidate_ops = { "Identity", - "Snapshot", "Const", "Conv2D", "MaxPool", @@ -75,7 +74,7 @@ bool IsTensorRTCandidate(const tensorflow::Node* node) { // TODO(ben,jie): ... }; // LINT.ThenChange(//tensorflow/contrib/tensorrt/convert/convert_nodes.h) - return candidate_ops.count(node->type_string()); + return candidate_ops.count(node_def.op()); } void GetSubGraphIncomingEdges(const tensorflow::Graph& graph, @@ -85,10 +84,10 @@ void GetSubGraphIncomingEdges(const tensorflow::Graph& graph, const tensorflow::Node* node = graph.FindNodeId(node_id); for (const tensorflow::Edge* edge : node->in_edges()) { if (!subgraph_node_ids.count(edge->src()->id()) && - !edge->src()->IsSource() && !edge->IsControlEdge()) { + !edge->src()->IsSource()) { incoming_edges->insert(edge); } else { - VLOG(2) << node->name() << " -> " << edge->src()->name() << " N, "; + VLOG(2) << edge->src()->name() << " N, "; } } } @@ -101,11 +100,11 @@ void GetSubGraphOutgoingEdges(const tensorflow::Graph& graph, const tensorflow::Node* node = graph.FindNodeId(node_id); for (const tensorflow::Edge* edge : node->out_edges()) { if (!subgraph_node_ids.count(edge->dst()->id()) && - !edge->dst()->IsSink() && !edge->IsControlEdge()) { - VLOG(2) << node->name() << " -> " << edge->dst()->name() << " Y, "; + !edge->dst()->IsSink()) { + VLOG(2) << edge->dst()->name() << " Y, "; outgoing_edges->insert(edge); } else { - VLOG(2) << node->name() << " -> " << edge->dst()->name() << " N, "; + VLOG(2) << edge->dst()->name() << " N, "; } } } @@ -410,9 +409,8 @@ tensorflow::Status ConvertGraphDefToTensorRT( tensorflow::Status status = ConvertSubGraphToTensorRT(&p); if (status != tensorflow::Status::OK()) { LOG(WARNING) << "subgraph conversion error for subgraph_index:" << count - << " due to: \"" << status.ToString() - << "\" SKIPPING......( " << subgraph_node_names.size() - << " nodes)"; + << " due to: \n" + << status.ToString() << " SKIPPING......"; } count++; } diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 370911e4d9..92a692baa7 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -53,8 +53,8 @@ limitations under the License. namespace tensorflow { namespace tensorrt { namespace convert { -using ::tensorflow::strings::StrAppend; using ::tensorflow::strings::StrCat; + namespace { inline tensorflow::Status ConvertDType(tensorflow::DataType tf_dtype, @@ -430,8 +430,9 @@ class Converter { tensorflow::tensorrt::TRTWeightStore* weight_store_; bool fp16_; void register_op_converters(); - tensorflow::Status get_inputs(const tensorflow::NodeDef& node_def, - std::vector* inputs) { + std::vector get_inputs( + const tensorflow::NodeDef& node_def) { + std::vector inputs; for (auto const& input_name : node_def.input()) { /************************************************************************* * TODO(jie) handle case 1) here @@ -452,17 +453,13 @@ class Converter { VLOG(2) << "retrieve input: " << name; if (trt_tensors_.count(name)) { - inputs->push_back(trt_tensors_.at(name)); + inputs.push_back(trt_tensors_.at(name)); } else { - string str("Node "); - StrAppend(&str, node_def.name(), " should have an input named '", name, - "' but it is not available"); - LOG(WARNING) << "input: " << name << " not available for node at " - << node_def.name(); - return tensorflow::errors::InvalidArgument(str); + LOG(FATAL) << "input: " << name << " not available for node at, " + << node_def.name(); } } - return tensorflow::Status::OK(); + return inputs; } public: @@ -486,8 +483,7 @@ class Converter { } tensorflow::Status convert_node(const tensorflow::NodeDef& node_def) { - std::vector inputs; - TF_RETURN_IF_ERROR(this->get_inputs(node_def, &inputs)); + std::vector inputs = this->get_inputs(node_def); string op = node_def.op(); if (!op_registry_.count(op)) { return tensorflow::errors::Unimplemented( @@ -552,19 +548,6 @@ class Converter { } }; -TRT_ShapedWeights ConvertFP32ToFP16(Converter& ctx, - const TRT_ShapedWeights& weights_src) { - auto dtype_new = tensorflow::DataType::DT_HALF; - TRT_ShapedWeights weights = - ctx.get_temp_weights(dtype_new, weights_src.shape_); - const float* src = static_cast(weights_src.GetValues()); - Eigen::half* dst = const_cast( - static_cast(weights.GetValues())); - for (int64_t i = 0; i < weights_src.count(); i++) { - dst[i] = Eigen::half_impl::float_to_half_rtne(src[i]); - } - return weights; -} // **************************************************************************** // Constant folding functions // TODO(jie): once optimizer kicks in, we should have done constant folding @@ -892,7 +875,7 @@ tensorflow::Status BinaryTensorOpWeight( // Check type consistency nvinfer1::DataType ttype; - TF_RETURN_IF_ERROR(ConvertDType(weights.type_, &ttype)); + TF_CHECK_OK(ConvertDType(weights.type_, &ttype)); // Check scale mode auto dims_w = weights.shape_; @@ -974,10 +957,6 @@ tensorflow::Status BinaryTensorOpWeight( } } - if (ctx.isFP16()) { - weights = ConvertFP32ToFP16(ctx, weights); - } - // prepare weights TRT_ShapedWeights shift_weights(weights.type_); TRT_ShapedWeights scale_weights(weights.type_); @@ -1019,7 +998,9 @@ enum class ConvolutionType { DEFAULT, DEPTHWISE_CONV }; tensorflow::Status ConvertConv2DHelper( Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, - std::vector* outputs, int group) { + std::vector* outputs, + int group // group ==0 specifies depthwise conv +) { const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); TFAttrs attrs(node_def); @@ -1044,10 +1025,6 @@ tensorflow::Status ConvertConv2DHelper( VLOG(2) << "groups count: " << num_groups; TRT_ShapedWeights weights_rsck = inputs.at(1).weights(); - if (ctx.isFP16()) { - weights_rsck = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); - } - TRT_ShapedWeights weights = ctx.get_temp_weights_like(weights_rsck); ReorderRSCKToKCRS(weights_rsck, &weights, num_groups); TRT_ShapedWeights biases(weights.type_); @@ -1157,9 +1134,9 @@ tensorflow::Status BinaryTensorOpTensor( CHECK_EQ_TYPE(tensor_r->getType(), dtype); auto op_pair = ops.find(node_def.op()); if (op_pair == ops.end()) - return tensorflow::errors::Unimplemented( - "binary op: " + node_def.op() + - " not supported at: " + node_def.name()); + return tensorflow::errors::Unimplemented("binary op: " + node_def.op() + + " not supported at: " + + node_def.name()); nvinfer1::IElementWiseLayer* layer = ctx.network()->addElementWise( *const_cast(tensor_l), @@ -1318,11 +1295,8 @@ tensorflow::Status ConvertScale(Converter& ctx, // Implement tensor binaryOp weight [channel wise] for now; const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); + // TODO(jie): handle NHWC/NCHW transpose; TRT_ShapedWeights weights = inputs.at(1).weights(); - if (ctx.isFP16()) { - weights = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); - } - TRT_ShapedWeights empty_weights(weights.type_); TFAttrs attrs(node_def); @@ -1402,11 +1376,8 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.d[0] = weights_tensor.float_val_size(); scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; } else { - LOG(WARNING) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); - string err_str("Broadcast method is not supported for '"); - StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); - return tensorflow::errors::InvalidArgument(err_str); + LOG(FATAL) << "Broadcast on weights only supports kCHANNEL and" + << " kUNIFORM, at: " << node_def.name(); } } } else { @@ -1420,16 +1391,33 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.float_val().begin(), - weights_tensor.float_val() - .end()); // make a local copy first to flatten - memcpy(dst, tensor_data.data(), len_data); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); + if (ctx.isFP16()) { + auto dtype_new = tensorflow::DataType::DT_HALF; + size_t len_data = tensorflow::DataTypeSize(dtype_new); + for (int i = 0; i < scalar_shape.nbDims; i++) + len_data *= scalar_shape.d[i]; + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); + auto half_tensor = temp_tensor.flat(); + Eigen::DefaultDevice defd; + half_tensor.device(defd) = + tensor.flat().template cast(); + memcpy(dst, half_tensor.data(), len_data); // store into weight store + weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); + } else { + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) + len_data *= scalar_shape.d[i]; + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.float_val().begin(), + weights_tensor.float_val() + .end()); // make a local copy first to flatten + memcpy(dst, tensor_data.data(), len_data); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); + } } else if (!weights_tensor.int_val().empty()) { VLOG(2) << "int!!!" << node_def.name(); nvinfer1::Dims scalar_shape; @@ -1444,11 +1432,8 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.d[0] = weights_tensor.int_val_size(); scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; } else { - LOG(WARNING) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); - string err_str("Broadcast method is not supported for '"); - StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); - return tensorflow::errors::InvalidArgument(err_str); + LOG(FATAL) << "Broadcast on weights only supports kCHANNEL and" + << " kUNIFORM, at: " << node_def.name(); } } } else { @@ -1462,23 +1447,62 @@ tensorflow::Status ConvertConst(Converter& ctx, scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; } } - // we should not have converted //if (ctx.isFP16()) { - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; - size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); - len_data = std::max(len_data, len_tensor); - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - std::vector tensor_data( - weights_tensor.int_val().begin(), - weights_tensor.int_val().end()); // make a local copy first to flatten - // doesn't have to be contigous - memcpy(dst, tensor_data.data(), len_tensor); // store into weight store - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); + if (ctx.isFP16()) { + auto dtype_new = tensorflow::DataType::DT_HALF; + size_t len_data = tensorflow::DataTypeSize(dtype_new); + for (int i = 0; i < scalar_shape.nbDims; i++) + len_data *= scalar_shape.d[i]; + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + tensorflow::Tensor temp_tensor(tensorflow::DT_HALF, tensor.shape()); + TTypes::Flat half_tensor = temp_tensor.flat(); + Eigen::DefaultDevice defd; + switch (dtype) { + case (tensorflow::DT_INT32): { + half_tensor.device(defd) = + tensor.flat().template cast(); + break; + } + case (tensorflow::DT_INT16): { + half_tensor.device(defd) = + tensor.flat().template cast(); + break; + } + case (tensorflow::DT_INT8): { + half_tensor.device(defd) = + tensor.flat().template cast(); + break; + } + case (tensorflow::DT_UINT8): { + half_tensor.device(defd) = + tensor.flat().template cast(); + break; + } + default: + return tensorflow::errors::InvalidArgument( + "Datatype " + tensorflow::DataTypeString(dtype) + + " for FP16 conversion"); + break; + }; + memcpy(dst, half_tensor.data(), len_data); // store into weight store + weights = TRT_ShapedWeights(dtype_new, dst, scalar_shape); + } else { + size_t len_data = tensorflow::DataTypeSize(dtype); + for (int i = 0; i < scalar_shape.nbDims; i++) + len_data *= scalar_shape.d[i]; + size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); + len_data = std::max(len_data, len_tensor); + ctx.weight_store()->store_.push_back(std::vector(len_data)); + void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); + std::vector tensor_data( + weights_tensor.int_val().begin(), + weights_tensor.int_val() + .end()); // make a local copy first to flatten + // doesn't have to be contiguous + memcpy(dst, tensor_data.data(), len_tensor); // store into weight store + weights = TRT_ShapedWeights(dtype, dst, scalar_shape); + } } else if (!weights_tensor.tensor_content().empty()) { - // obsolete method. - // After optimization path, we do not see weights in this format. - // fp16 conversion technically should be needed here. VLOG(2) << "TENSOR!!!" << node_def.name(); const auto& content = weights_tensor.tensor_content(); @@ -1760,6 +1784,8 @@ tensorflow::Status ConvertConcat(Converter& ctx, TRT_ShapedWeights axis = inputs.at(input_size).weights(); TFAttrs attrs(node_def); + // auto attr_size = attrs.at("N")->i(); + // auto data_type = attrs.get("T"); auto index_type = attrs.get("Tidx"); // TODO(jie): handle data type @@ -1849,103 +1875,71 @@ tensorflow::Status ConvertFusedBatchNorm( "only is_training=false is supported, at " + node_def.name()); } nvinfer1::ITensor const* tensor = inputs.at(0).tensor(); - - // Check parameter types - auto parameter_type = inputs.at(1).weights().type_; - if ((parameter_type != tensorflow::DataType::DT_FLOAT) && - (parameter_type != tensorflow::DataType::DT_HALF)) { - return tensorflow::errors::Unimplemented( - "only float32 or float16 weight data type is supported, for node " + - node_def.name() + " got " + tensorflow::DataTypeString(parameter_type)); - } - for (int i = 1; i < 5; i++) { - if (inputs.at(i).weights().type_ != parameter_type) { - return tensorflow::errors::Unimplemented( - "Inconsistent parameter type for batchnormis not supported, at: " + - node_def.name()); - } - } - - TRT_ShapedWeights dummy_power_weights(parameter_type); - size_t nweight = 0; - for (int i = 1; i < 5; i++) { - nweight = std::max(nweight, (size_t)inputs.at(i).weights().count()); - } - TRT_ShapedWeights* ptr_shape_weights = nullptr; - for (int i = 1; i < 5; i++) { - if (inputs.at(i).weights().count() == nweight) { - ptr_shape_weights = - const_cast(&(inputs.at(i).weights())); - } else if (inputs.at(i).weights().count() != 1) { - return tensorflow::errors::InvalidArgument( - "Inconsistent batchnorm parameter count, at: " + node_def.name()); - } - } - // We could technically have two weights with different shape. - // that requires two addScale op, arguably less performant + TRT_ShapedWeights scale_weights = inputs.at(1).weights(); + TRT_ShapedWeights offset_weights = inputs.at(2).weights(); + TRT_ShapedWeights mean_weights = inputs.at(3).weights(); + TRT_ShapedWeights variance_weights = inputs.at(4).weights(); + TRT_ShapedWeights dummy_power_weights(scale_weights.type_); TRT_ShapedWeights combined_scale_weights = - ctx.get_temp_weights_like(*ptr_shape_weights); + ctx.get_temp_weights_like(scale_weights); TRT_ShapedWeights combined_offset_weights = - ctx.get_temp_weights_like(*ptr_shape_weights); - - const Eigen::half* cast_vals_array[4]; - const float* vals_array[4]; - for (int j = 0; j < 4; j++) { - cast_vals_array[j] = - static_cast(inputs.at(j + 1).weights().GetValues()); - vals_array[j] = - static_cast(inputs.at(j + 1).weights().GetValues()); - } - Eigen::half* cast_combined_scale_vals = const_cast( - static_cast(combined_scale_weights.GetValues())); - Eigen::half* cast_combined_offset_vals = const_cast( - static_cast(combined_offset_weights.GetValues())); - float* combined_scale_vals = const_cast( - static_cast(combined_scale_weights.GetValues())); - float* combined_offset_vals = const_cast( - static_cast(combined_offset_weights.GetValues())); - - for (size_t i = 0; i < nweight; ++i) { - float batchnorm_data[4]; - for (int j = 0; j < 4; j++) { - if (inputs.at(j + 1).weights().count() != 1) { - if (parameter_type == tensorflow::DT_FLOAT) { - batchnorm_data[j] = vals_array[j][i]; - } else if (parameter_type == tensorflow::DT_HALF) { - batchnorm_data[j] = - Eigen::half_impl::half_to_float(cast_vals_array[j][i]); - } - } else { - if (parameter_type == tensorflow::DT_FLOAT) { - batchnorm_data[j] = vals_array[j][0]; - } else if (parameter_type == tensorflow::DT_HALF) { - batchnorm_data[j] = - Eigen::half_impl::half_to_float(cast_vals_array[j][0]); - } - } + ctx.get_temp_weights_like(offset_weights); + size_t nweight = scale_weights.count(); + if ((scale_weights.type_ == offset_weights.type_) && + (mean_weights.type_ == variance_weights.type_) && + (scale_weights.type_ == variance_weights.type_)) { + if ((scale_weights.type_ != tensorflow::DataType::DT_FLOAT) && + (scale_weights.type_ != tensorflow::DataType::DT_HALF)) { + return tensorflow::errors::Unimplemented( + "only float32 or float16 weight data type is supported, for node " + + node_def.name() + " got " + + tensorflow::DataTypeString(scale_weights.type_)); } - float scale = batchnorm_data[0]; - float offset = batchnorm_data[1]; - float mean = batchnorm_data[2]; - float variance = batchnorm_data[3]; - float combined_scale_val = scale / sqrtf(variance + epsilon); - float combined_offset_val = offset - mean * combined_scale_val; - if (parameter_type == tensorflow::DT_FLOAT) { - combined_scale_vals[i] = combined_scale_val; - combined_offset_vals[i] = combined_offset_val; - } else if (parameter_type == tensorflow::DT_HALF) { - cast_combined_scale_vals[i] = Eigen::half(combined_scale_val); - cast_combined_offset_vals[i] = Eigen::half(combined_offset_val); + if (scale_weights.type_ == tensorflow::DT_FLOAT) { + for (size_t i = 0; i < nweight; ++i) { + float scale = (static_cast(scale_weights.GetValues()))[i]; + float offset = + (static_cast(offset_weights.GetValues()))[i]; + float mean = (static_cast(mean_weights.GetValues()))[i]; + float variance = + (static_cast(variance_weights.GetValues()))[i]; + float& combined_scale_ref = const_cast( + static_cast(combined_scale_weights.GetValues()))[i]; + float& combined_offset_ref = const_cast( + static_cast(combined_offset_weights.GetValues()))[i]; + combined_scale_ref = scale / sqrtf(variance + epsilon); + combined_offset_ref = offset - mean * combined_scale_ref; + } + } else { + const Eigen::half* scale_vals = + (static_cast(scale_weights.GetValues())); + const Eigen::half* off_vals = + (static_cast(offset_weights.GetValues())); + const Eigen::half* mean_vals = + (static_cast(mean_weights.GetValues())); + const Eigen::half* variance_vals = + (static_cast(variance_weights.GetValues())); + Eigen::half* comb_scale_vals = const_cast( + static_cast(combined_scale_weights.GetValues())); + Eigen::half* comb_off_vals = const_cast( + static_cast(combined_offset_weights.GetValues())); + for (size_t i = 0; i < nweight; ++i) { + float scale(scale_vals[i]); + float offset(off_vals[i]); + float mean(mean_vals[i]); + float variance(variance_vals[i]); + float combined_scale_ref = scale / sqrtf(variance + epsilon); + comb_scale_vals[i] = Eigen::half(combined_scale_ref); + float combined_offset_ref = offset - mean * combined_scale_ref; + comb_off_vals[i] = Eigen::half(combined_offset_ref); + } } } - - nvinfer1::ScaleMode mode = nweight == 1 ? nvinfer1::ScaleMode::kUNIFORM - : nvinfer1::ScaleMode::kCHANNEL; - nvinfer1::IScaleLayer* layer = - ctx.network()->addScale(*const_cast(tensor), mode, - combined_offset_weights.GetWeightsForTRT(), - combined_scale_weights.GetWeightsForTRT(), - dummy_power_weights.GetWeightsForTRT()); + nvinfer1::IScaleLayer* layer = ctx.network()->addScale( + *const_cast(tensor), nvinfer1::ScaleMode::kCHANNEL, + combined_offset_weights.GetWeightsForTRT(), + combined_scale_weights.GetWeightsForTRT(), + dummy_power_weights.GetWeightsForTRT()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); @@ -2056,7 +2050,6 @@ void Converter::register_op_converters() { op_registry_["Const"] = ConvertConst; // TODO(ben,jie): this is a temp hack. op_registry_["Identity"] = ConvertIdentity; // Identity should be removed - op_registry_["Snapshot"] = ConvertIdentity; // Snapshot should be removed // resnet_50_v1 slim implementation op_registry_["Add"] = ConvertBinary; @@ -2150,11 +2143,8 @@ tensorflow::Status ConvertCalibrationNodeToEngineNode( calib_res->thr_->join(); delete calib_res->thr_; if (!calib_res->engine_) { - LOG(ERROR) << "Calibration failed!, engine does not exist. Did you run " + LOG(FATAL) << "Calibration failed!, engine is nullptr. Did you run " "calibration graph?"; - return tensorflow::errors::FailedPrecondition( - "Calibration graph needs to be executed on" - " calibration data before convertsion to inference graph"); } auto weight_rmgr = trt_rm->getManager("WeightStore"); TF_CHECK_OK(weight_rmgr->Delete( @@ -2191,7 +2181,7 @@ tensorflow::Status ConvertCalibrationNodeToEngineNode( return status; } auto trt_engine_node = graph.AddNode(engine_node, &status); - TF_RETURN_IF_ERROR(status); + TF_CHECK_OK(status); for (size_t i = 0; i < out_edges.size(); i++) { VLOG(1) << "Connecting trt_engine_node output " << i << " with " << out_edges.at(i)->dst()->name() << " port " @@ -2289,12 +2279,6 @@ tensorflow::Status InjectCalibrationNode(tensorrt::convert::SubGraphParams& s) { input_dtypes.push_back(tf_dtype); nvinfer1::DataType dtype(nvinfer1::DataType::kFLOAT); - auto type_status = ConvertDType(tf_dtype, &dtype); - if (type_status != tensorflow::Status::OK()) { - LOG(WARNING) << "Data type conversion for input '" << node_name - << "' failed"; - return type_status; - } TF_CHECK_OK(ConvertDType(tf_dtype, &dtype)); VLOG(2) << "accessing output index of: " << output_idx @@ -2362,8 +2346,8 @@ tensorflow::Status InjectCalibrationNode(tensorrt::convert::SubGraphParams& s) { output_names.push_back(tensor_name); auto tensor_or_weights = converter.get_tensor(tensor_name); if (!tensor_or_weights.is_tensor()) { - return tensorflow::errors::InvalidArgument("Output node'" + tensor_name + - "' is weights not tensor"); + return tensorflow::errors::InvalidArgument( + "Output node is weights not tensor"); } nvinfer1::ITensor* tensor = tensor_or_weights.tensor(); if (!tensor) { @@ -2520,11 +2504,7 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( input_dtypes.push_back(tf_dtype); nvinfer1::DataType dtype(nvinfer1::DataType::kFLOAT); - auto type_status = ConvertDType(tf_dtype, &dtype); - if (type_status != tensorflow::Status::OK()) { - LOG(WARNING) << "Type conversion failed for " << node_name; - return type_status; - } + TF_CHECK_OK(ConvertDType(tf_dtype, &dtype)); VLOG(2) << "Accessing output index of: " << output_idx << ", at node: " << node_name @@ -2535,12 +2515,8 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( // TODO(jie): TRT 3.x only support 4 dimensional input tensor. // update the code once TRT 4.0 comes out. - if (op_info.shape().dim_size() != 4) { - string err_str = "Require 4 dimensional input."; - StrAppend(&err_str, " Got ", op_info.shape().dim_size(), " ", - shape_inference_node_name); - return tensorflow::errors::Unimplemented(err_str); - } + if (op_info.shape().dim_size() != 4) + return tensorflow::errors::Unimplemented("require 4 dimensional input"); for (int i = 1; i < op_info.shape().dim_size(); i++) { VLOG(2) << "dimension: " << i @@ -2601,8 +2577,8 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( output_names.push_back(tensor_name); auto tensor_or_weights = converter.get_tensor(tensor_name); if (!tensor_or_weights.is_tensor()) { - return tensorflow::errors::InvalidArgument("Output node '" + tensor_name + - "' is weights not tensor"); + return tensorflow::errors::InvalidArgument( + "Output node is weights not tensor"); } nvinfer1::ITensor* tensor = tensor_or_weights.tensor(); if (!tensor) { @@ -2646,8 +2622,7 @@ tensorflow::Status ConvertSubGraphToTensorRTNodeDef( } TF_RETURN_IF_ERROR(weight_rmgr->Delete( engine_name, engine_name)); - LOG(INFO) << "finished engine " << engine_name << " containing " - << s.subgraph_node_ids.size() << " nodes"; + LOG(INFO) << "finished engine " << engine_name; // Build the TRT op tensorflow::NodeDefBuilder op_builder(engine_name, "TRTEngineOp"); diff --git a/tensorflow/contrib/tensorrt/segment/segment.cc b/tensorflow/contrib/tensorrt/segment/segment.cc index 8fc4697c51..6193f0b0a1 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.cc +++ b/tensorflow/contrib/tensorrt/segment/segment.cc @@ -80,20 +80,13 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, std::vector in_edges(dst->in_edges().begin(), dst->in_edges().end()); for (const tensorflow::Edge* in_edge : in_edges) { - if (in_edge->IsControlEdge()) { - if (in_edge->src() != src) { - tensorflow::Edge* e = const_cast(in_edge); - graph->AddControlEdge(e->src(), src); - } - } else { - if (in_edge->src() != src) { - tensorflow::Edge* e = const_cast(in_edge); - if (e->src() == graph->source_node()) { - graph->AddEdge(e->src(), e->src_output(), src, - tensorflow::Graph::kControlSlot); - } else { - graph->AddEdge(e->src(), e->src_output(), src, 0 /* input index */); - } + if (in_edge->src() != src) { + tensorflow::Edge* e = const_cast(in_edge); + if (e->src() == graph->source_node()) { + graph->AddEdge(e->src(), e->src_output(), src, + tensorflow::Graph::kControlSlot); + } else { + graph->AddEdge(e->src(), e->src_output(), src, 0 /* input index */); } } } @@ -101,19 +94,12 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, std::vector out_edges(dst->out_edges().begin(), dst->out_edges().end()); for (const tensorflow::Edge* out_edge : out_edges) { - if (out_edge->IsControlEdge()) { - tensorflow::Edge* e = const_cast(out_edge); - graph->AddControlEdge(src, e->dst()); + tensorflow::Edge* e = const_cast(out_edge); + if (e->dst() == graph->sink_node()) { + graph->AddEdge(src, tensorflow::Graph::kControlSlot, e->dst(), + e->dst_input()); } else { - tensorflow::Edge* e = const_cast(out_edge); - if (e->dst() == graph->sink_node()) { - VLOG(1) << " edge to sink node " << src->name() << " -> " - << e->dst()->name(); - graph->AddEdge(src, tensorflow::Graph::kControlSlot, e->dst(), - e->dst_input()); - } else { - graph->AddEdge(src, 0 /* output index */, e->dst(), e->dst_input()); - } + graph->AddEdge(src, 0 /* output index */, e->dst(), e->dst_input()); } } @@ -132,7 +118,7 @@ void ContractEdge(tensorflow::Edge* edge, tensorflow::Graph* graph, tensorflow::Status SegmentGraph( const tensorflow::GraphDef& gdef, - const std::function& candidate_fn, + const std::function& candidate_fn, const SegmentOptions& options, SegmentNodesVector* segments) { // Create a Graph representation of the GraphDef. tensorflow::FunctionLibraryDefinition flib(tensorflow::OpRegistry::Global(), @@ -150,7 +136,7 @@ tensorflow::Status SegmentGraph( for (int i = 0; i < graph.num_node_ids(); ++i) { tensorflow::Node* node = graph.FindNodeId(i); if (options.exclude_node_list.count(node->name()) != 0 || - !candidate_fn(node)) { + !candidate_fn(node->def())) { node = nullptr; } node_segments.emplace_back(node); @@ -169,7 +155,7 @@ tensorflow::Status SegmentGraph( for (const tensorflow::Node* node : order) { // All output nodes of 'node' have been visited... - VLOG(2) << "Trying node " << node->name() << " id=" << node->id(); + VLOG(2) << "Trying node " << node->name(); // 'node' must be a TRT candidate... if (node_segments[node->id()].Value() == nullptr) { @@ -183,12 +169,8 @@ tensorflow::Status SegmentGraph( while (true) { std::set contract_edges; for (const tensorflow::Edge* out_edge : node->out_edges()) { - VLOG(2) << "... out node " << out_edge->dst()->name() << " ( " - << out_edge->dst()->id() << " <- " << node->id() << " )"; - if (out_edge->IsControlEdge()) { - VLOG(2) << "... ... Control Edge, Skipping"; - continue; - } + VLOG(2) << "... out node " << out_edge->dst()->name(); + // Out node must be TRT candidate... if (node_segments[out_edge->dst()->id()].Value() == nullptr) { VLOG(2) << "... ... not a TRT candidate"; @@ -214,8 +196,7 @@ tensorflow::Status SegmentGraph( const tensorflow::Node* src = contract_edge->src(); const tensorflow::Node* dst = contract_edge->dst(); - VLOG(2) << "Merge " << src->name() << " <- " << dst->name() << " (" - << src->id() << " <- " << dst->id(); + VLOG(2) << "Merge " << src->name() << " <- " << dst->name(); node_segments[src->id()].Merge(&node_segments[dst->id()]); // Contracting the edge leaves disconnected graph edges. diff --git a/tensorflow/contrib/tensorrt/segment/segment.h b/tensorflow/contrib/tensorrt/segment/segment.h index 7e8685f44a..ee6e2b3ed2 100644 --- a/tensorflow/contrib/tensorrt/segment/segment.h +++ b/tensorflow/contrib/tensorrt/segment/segment.h @@ -20,12 +20,10 @@ limitations under the License. #include #include "tensorflow/core/framework/graph.pb.h" -#include "tensorflow/core/graph/graph.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/types.h" namespace tensorflow { - namespace tensorrt { namespace segment { @@ -48,7 +46,7 @@ struct SegmentOptions { // @return the status. tensorflow::Status SegmentGraph( const tensorflow::GraphDef& gdef, - const std::function& candidate_fn, + const std::function& candidate_fn, const SegmentOptions& options, SegmentNodesVector* segments); } // namespace segment diff --git a/tensorflow/contrib/tensorrt/segment/segment_test.cc b/tensorflow/contrib/tensorrt/segment/segment_test.cc index 7ddabec268..74cbc5f2b3 100644 --- a/tensorflow/contrib/tensorrt/segment/segment_test.cc +++ b/tensorflow/contrib/tensorrt/segment/segment_test.cc @@ -35,7 +35,7 @@ class SegmentTest : public ::testing::Test { TF_Operation* Add(TF_Operation* l, TF_Operation* r, TF_Graph* graph, TF_Status* s, const char* name); - std::function MakeCandidateFn( + std::function MakeCandidateFn( const std::set& node_names); protected: @@ -60,10 +60,10 @@ bool SegmentTest::GetGraphDef(TF_Graph* graph, return ret; } -std::function SegmentTest::MakeCandidateFn( +std::function SegmentTest::MakeCandidateFn( const std::set& node_names) { - return [node_names](const Node* node) -> bool { - return node_names.find(node->name()) != node_names.end(); + return [node_names](const NodeDef& node) -> bool { + return node_names.find(node.name()) != node_names.end(); }; } diff --git a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py index 4f6527a546..ff140efd48 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/ar_model.py +++ b/tensorflow/contrib/timeseries/python/timeseries/ar_model.py @@ -70,7 +70,7 @@ class ARModel(model.TimeSeriesModel): input_window_size: Number of past time steps of data to look at when doing the regression. output_window_size: Number of future time steps to predict. Note that - setting it to > 1 empirically seems to give a better fit. + setting it to > 1 empiricaly seems to give a better fit. num_features: number of input features per time step. num_time_buckets: Number of buckets into which to divide (time % periodicity) for generating time based features. diff --git a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py index 26793c80bf..23452a81c3 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/math_utils.py +++ b/tensorflow/contrib/timeseries/python/timeseries/math_utils.py @@ -185,7 +185,7 @@ def batch_matrix_pow(matrices, powers): { matmul(A, power(matmul(A, A), (p - 1) / 2)) for odd p power(A, 0) = I - The power(A, 0) = I case is handled by starting with accumulator set to the + The power(A, 0) = I case is handeled by starting with accumulator set to the identity matrix; matrices with zero residual powers are passed through unchanged. diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py index 6746dd7b43..1afc58cfb2 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/varma.py @@ -107,7 +107,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state transition matrix. It has shape - [self.state_dimension, self.state_dimension]. + [self.state_dimendion, self.state_dimension]. """ # Pad any unused AR blocks with zeros. The extra state is necessary if # ma_order >= ar_order. @@ -127,7 +127,7 @@ class VARMA(state_space_model.StateSpaceModel): Returns: the state noise transform matrix. It has shape - [self.state_dimension, self.num_features]. + [self.state_dimendion, self.num_features]. """ # Noise is broadcast, through the moving average coefficients, to # un-observed parts of the latent state. diff --git a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt index e667c328ae..51d91399f8 100644 --- a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt @@ -49,14 +49,14 @@ in the batch: If `fast` is `True`, then the solution is computed by solving the normal equations using Cholesky decomposition. Specifically, if \\(m \ge n\\) then \\(X = (A^H A + \lambda I)^{-1} A^H B\\), which solves the least-squares -problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + \lambda ||Z||_F^2\\). -If \\(m \lt n\\) then `output` is computed as +problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + +\lambda ||Z||_F^2\\). If \\(m \lt n\\) then `output` is computed as \\(X = A^H (A A^H + \lambda I)^{-1} B\\), which (for \\(\lambda = 0\\)) is the minimum-norm solution to the under-determined linear system, i.e. \\(X = \mathrm{argmin}_{Z \in \mathbb{C}^{n \times k} } ||Z||_F^2 \\), subject to \\(A Z = B\\). Notice that the fast path is only numerically stable when \\(A\\) is numerically full rank and has a condition number -\\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or \\(\lambda\\) is +\\(\mathrm{cond}(A) \lt \frac{1}{\sqrt{\epsilon_{mach} } }\\) or\\(\lambda\\) is sufficiently large. If `fast` is `False` an algorithm based on the numerically robust complete diff --git a/tensorflow/core/common_runtime/mkl_cpu_allocator.cc b/tensorflow/core/common_runtime/mkl_cpu_allocator.cc index 829c19204a..43a909466e 100644 --- a/tensorflow/core/common_runtime/mkl_cpu_allocator.cc +++ b/tensorflow/core/common_runtime/mkl_cpu_allocator.cc @@ -19,6 +19,9 @@ limitations under the License. namespace tensorflow { +constexpr const char* MklCPUAllocator::kMaxLimitStr; +constexpr const size_t MklCPUAllocator::kDefaultMaxLimit; + } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/framework/common_shape_fns.cc b/tensorflow/core/framework/common_shape_fns.cc index 2fb17c2b02..623248b6ce 100644 --- a/tensorflow/core/framework/common_shape_fns.cc +++ b/tensorflow/core/framework/common_shape_fns.cc @@ -1210,7 +1210,7 @@ Status ConcatV2Shape(InferenceContext* c) { c->num_inputs() - 1 /* dim_index */); } -Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index) { +Status BroadcastBinaryOpShapeFn(InferenceContext* c) { ShapeHandle shape_x = c->input(0); ShapeHandle shape_y = c->input(1); if (!c->RankKnown(shape_x) || !c->RankKnown(shape_y)) { @@ -1272,7 +1272,7 @@ Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index) { } } - c->set_output(output_index, c->MakeShape(dims)); + c->set_output(0, c->MakeShape(dims)); return Status::OK(); } diff --git a/tensorflow/core/framework/common_shape_fns.h b/tensorflow/core/framework/common_shape_fns.h index 789746b403..293c40e04d 100644 --- a/tensorflow/core/framework/common_shape_fns.h +++ b/tensorflow/core/framework/common_shape_fns.h @@ -265,15 +265,9 @@ Status ConcatShape(shape_inference::InferenceContext* c, // Shape function for concat operations. Status ConcatV2Shape(shape_inference::InferenceContext* c); -// Shape function for binary operators that broadcast their inputs -// and with output to output_index. -Status BroadcastBinaryOpOutputShapeFn(InferenceContext* c, int output_index); - // Shape function for binary operators that broadcast their inputs. // Tested by ops/math_ops_test.cc. -inline Status BroadcastBinaryOpShapeFn(InferenceContext* c) { - return BroadcastBinaryOpOutputShapeFn(c, 0); -} +Status BroadcastBinaryOpShapeFn(InferenceContext* c); // Shape function for random operations. Status RandomShape(shape_inference::InferenceContext* c); diff --git a/tensorflow/core/framework/shape_inference.h b/tensorflow/core/framework/shape_inference.h index accc587000..e3cc848a16 100644 --- a/tensorflow/core/framework/shape_inference.h +++ b/tensorflow/core/framework/shape_inference.h @@ -317,7 +317,6 @@ class InferenceContext { input_tensors_as_shapes_ = input_tensors_as_shapes; } - ShapeHandle output(int64 idx) const { return outputs_[idx]; } void set_output(int idx, ShapeHandle shape) { outputs_[idx] = shape; } Status set_output(StringPiece output_name, const std::vector& shapes); diff --git a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc index 62aafa7930..333a6570dc 100644 --- a/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc +++ b/tensorflow/core/kernels/mkl_fused_batch_norm_op.cc @@ -933,7 +933,7 @@ class MklFusedBatchNormOp : public OpKernel { bool is_training_; T* mean_values_; T* variance_values_; - int depth_; // batch normalization is done for per channel. + size_t depth_; // batch normalization is done for per channel. void ExtractParams(OpKernelContext* context) { const Tensor& input = MklGetInput(context, 0); diff --git a/tensorflow/core/kernels/segment_reduction_ops.h b/tensorflow/core/kernels/segment_reduction_ops.h index 7badc00572..4abfbfb1a6 100644 --- a/tensorflow/core/kernels/segment_reduction_ops.h +++ b/tensorflow/core/kernels/segment_reduction_ops.h @@ -23,13 +23,6 @@ limitations under the License. // non-GPU targets. This only breaks in clang, because it's more strict for // template code and CudaAtomicMax is used in template context. -// This file requires the following include because it uses CudaAtomicMax: -// #include "tensorflow/core/util/cuda_kernel_helper.h" - -// Unfortunately we can't add the #include, since it breaks compilation for -// non-GPU targets. This only breaks in clang, because it's more strict for -// template code and CudaAtomicMax is used in template context. - #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" diff --git a/tensorflow/core/kernels/snapshot_op.cc b/tensorflow/core/kernels/snapshot_op.cc index fe04dcf72e..50157d5d48 100644 --- a/tensorflow/core/kernels/snapshot_op.cc +++ b/tensorflow/core/kernels/snapshot_op.cc @@ -22,26 +22,6 @@ limitations under the License. namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; -typedef Eigen::GpuDevice GPUDevice; - -template -class SnapshotOp : public OpKernel { - public: - explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} - - void Compute(OpKernelContext* context) override { - const Tensor& input = context->input(0); - Tensor* output = nullptr; - // Try to use buffer forwarding to avoid an explicit copy. - OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 0, input.shape(), &output)); - if (!output->SharesBufferWith(input)) { - functor::Snapshot functor; - functor(context->eigen_device(), input.flat(), - output->flat()); - } - } -}; #define REGISTER_KERNEL(TYPE) \ REGISTER_KERNEL_BUILDER( \ @@ -51,16 +31,6 @@ class SnapshotOp : public OpKernel { TF_CALL_POD_TYPES(REGISTER_KERNEL); #undef REGISTER_KERNEL -#if GOOGLE_CUDA -#define REGISTER_KERNEL(TYPE) \ - REGISTER_KERNEL_BUILDER( \ - Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ - SnapshotOp); - -TF_CALL_POD_TYPES(REGISTER_KERNEL); -#undef REGISTER_KERNEL -#endif - #if TENSORFLOW_USE_SYCL typedef Eigen::SyclDevice SyclDevice; #define REGISTER_SYCL_KERNEL(TYPE) \ diff --git a/tensorflow/core/kernels/snapshot_op.h b/tensorflow/core/kernels/snapshot_op.h index a18065d42b..b94834f159 100644 --- a/tensorflow/core/kernels/snapshot_op.h +++ b/tensorflow/core/kernels/snapshot_op.h @@ -26,19 +26,29 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" namespace tensorflow { -namespace functor { -// Functor used by SnapshotOp. template -struct Snapshot { - void operator()(const Device& device, - typename TTypes::ConstTensor input, - typename TTypes::Tensor output) { - device.memcpy(output.data(), input.data(), input.size() * sizeof(Scalar)); +class SnapshotOp : public OpKernel { + public: + explicit SnapshotOp(OpKernelConstruction* context) : OpKernel(context) {} + + void Compute(OpKernelContext* context) override { + const Tensor& input = context->input(0); + Tensor* output = nullptr; + // Try to use buffer forwarding to avoid an explicit copy. + OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( + {0}, 0, input.shape(), &output)); + if (!output->SharesBufferWith(input)) { + // We had to allocate a new buffer since the refcount on the input was + // greater than 1. Copy the input to the new buffer. + const Device& device = context->eigen_device(); + device.memcpy(output->template flat().data(), + input.template flat().data(), + input.NumElements() * sizeof(Scalar)); + } } }; -} // namespace functor } // namespace tensorflow #endif // TENSORFLOW_KERNELS_SNAPSHOT_OP_H_ diff --git a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc index e4e3bd5220..52070be838 100644 --- a/tensorflow/core/kernels/snapshot_op_gpu.cu.cc +++ b/tensorflow/core/kernels/snapshot_op_gpu.cu.cc @@ -24,10 +24,13 @@ limitations under the License. namespace tensorflow { typedef Eigen::GpuDevice GPUDevice; -// Definition of the GPU implementations declared in softsign_op.cc. -#define DEFINE_GPU_KERNELS(T) template struct functor::Snapshot; +#define REGISTER_KERNEL(TYPE) \ + REGISTER_KERNEL_BUILDER( \ + Name("Snapshot").Device(DEVICE_GPU).TypeConstraint("T"), \ + SnapshotOp); -TF_CALL_POD_TYPES(DEFINE_GPU_KERNELS); +TF_CALL_POD_TYPES(REGISTER_KERNEL); +#undef REGISTER_KERNEL } // namespace tensorflow diff --git a/tensorflow/core/kernels/xent_op.cc b/tensorflow/core/kernels/xent_op.cc index 9a3612bd72..a6a71fdfaf 100644 --- a/tensorflow/core/kernels/xent_op.cc +++ b/tensorflow/core/kernels/xent_op.cc @@ -17,14 +17,12 @@ limitations under the License. #define EIGEN_USE_THREADS +#include "tensorflow/core/kernels/xent_op.h" #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" - #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" -#include "tensorflow/core/kernels/xent_op.h" -#include "tensorflow/core/util/bcast.h" namespace tensorflow { @@ -43,56 +41,37 @@ class SoftmaxXentWithLogitsOp : public OpKernel { void Compute(OpKernelContext* context) override { const Tensor& logits_in = context->input(0); const Tensor& labels_in = context->input(1); - - TensorShape shape_in = logits_in.shape(); - - BCast bcast(BCast::FromShape(logits_in.shape()), - BCast::FromShape(labels_in.shape())); - if (!logits_in.IsSameSize(labels_in)) { - OP_REQUIRES(context, bcast.IsValid(), - errors::InvalidArgument( - "logits and labels must be broadcastable: logits_size=", - logits_in.shape().DebugString(), - " labels_size=", labels_in.shape().DebugString())); - shape_in = BCast::ToShape(bcast.output_shape()); - } - OP_REQUIRES(context, TensorShapeUtils::IsMatrix(shape_in), - errors::InvalidArgument("logits and labels must be beither " - "2-dimensional, or roadcasted to " - "2-dimensional")); + OP_REQUIRES(context, logits_in.IsSameSize(labels_in), + errors::InvalidArgument( + "logits and labels must be same size: logits_size=", + logits_in.shape().DebugString(), + " labels_size=", labels_in.shape().DebugString())); + OP_REQUIRES(context, TensorShapeUtils::IsMatrix(logits_in.shape()), + errors::InvalidArgument("logits must be 2-dimensional")); + // As we already tested that both inputs have the same shape no need to + // check that "labels" is a matrix too. // loss is 1-D (one per example), and size is batch_size. Tensor scratch; OP_REQUIRES_OK( context, context->allocate_temp(DataTypeToEnum::value, - TensorShape({shape_in.dim_size(0), 1}), + TensorShape({logits_in.dim_size(0), 1}), &scratch)); Tensor* loss_out = nullptr; OP_REQUIRES_OK(context, context->allocate_output( - 0, TensorShape({shape_in.dim_size(0)}), &loss_out)); + 0, TensorShape({logits_in.dim_size(0)}), &loss_out)); Tensor* back_out = nullptr; // Try to reuse the logits_in buffer for the backprop output. OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {0}, 1, shape_in, &back_out)); - if (shape_in.dim_size(0) > 0) { + {0}, 1, logits_in.shape(), &back_out)); + if (logits_in.dim_size(0) > 0) { functor::XentFunctor functor; - if (logits_in.IsSameSize(labels_in)) { - functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), - Eigen::array{1, 1}, - Eigen::array{1, 1}, logits_in.matrix(), - labels_in.matrix(), scratch.matrix(), loss_out->vec(), - back_out->matrix()); - } else { - functor(context->eigen_device(), shape_in.AsEigenDSizes<2>(), - BCast::ToIndexArray<2>(bcast.x_bcast()), - BCast::ToIndexArray<2>(bcast.y_bcast()), - logits_in.template shaped(bcast.x_reshape()), - labels_in.template shaped(bcast.y_reshape()), - scratch.matrix(), loss_out->vec(), back_out->matrix()); - } + functor(context->eigen_device(), logits_in.matrix(), + labels_in.matrix(), scratch.matrix(), loss_out->vec(), + back_out->matrix()); } } }; @@ -102,17 +81,13 @@ class SoftmaxXentWithLogitsOp : public OpKernel { namespace functor { template struct XentFunctorBase { - void operator()(const Device& d, - const Eigen::DSizes& shape, - const Eigen::array& logits_bcast, - const Eigen::array& labels_bcast, - typename TTypes::ConstMatrix logits, + void operator()(const Device& d, typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, - logits, labels, scratch, loss, backprop); + XentEigenImpl::Compute(d, logits, labels, scratch, loss, + backprop); } }; diff --git a/tensorflow/core/kernels/xent_op.h b/tensorflow/core/kernels/xent_op.h index 87be17fca9..e689fca7ff 100644 --- a/tensorflow/core/kernels/xent_op.h +++ b/tensorflow/core/kernels/xent_op.h @@ -18,7 +18,6 @@ limitations under the License. // Functor definition for XentOp, must be compilable by nvcc. #include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" - #include "tensorflow/core/framework/tensor_types.h" namespace tensorflow { @@ -34,11 +33,7 @@ struct XentFunctor { // scratch: temporary tensor, dims: batch_size, 1 // loss: output tensor for the loss, dims: batch_size. // backprop: output tensor for the backprop, dims: batch_size, num_classes. - void operator()(const Device &d, - const Eigen::DSizes &shape, - const Eigen::array &logits_bcast, - const Eigen::array &labels_bcast, - typename TTypes::ConstMatrix logits, + void operator()(const Device& d, typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -50,11 +45,7 @@ struct XentFunctor { // specializations for both device types. template struct XentEigenImpl { - static void Compute(const Device &d, - const Eigen::DSizes &shape, - const Eigen::array &logits_bcast, - const Eigen::array &labels_bcast, - typename TTypes::ConstMatrix logits, + static void Compute(const Device& d, typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, @@ -66,8 +57,8 @@ struct XentEigenImpl { const int kBatchDim = 0; const int kClassDim = 1; - const int batch_size = shape[kBatchDim]; - const int num_classes = shape[kClassDim]; + const int batch_size = logits.dimension(kBatchDim); + const int num_classes = logits.dimension(kClassDim); // These arrays are used to reduce along the class dimension, and broadcast // the resulting value to all classes. @@ -93,12 +84,10 @@ struct XentEigenImpl { #endif // max_logits along classes. - scratch.reshape(batch_only).device(d) = - logits.broadcast(logits_bcast).maximum(along_class); + scratch.reshape(batch_only).device(d) = logits.maximum(along_class); // logits - max_logits. - backprop.device(d) = - logits.broadcast(logits_bcast) - scratch.broadcast(one_by_class); + backprop.device(d) = logits - scratch.broadcast(one_by_class); // sum(exp(logits - max_logits)) along classes. scratch.reshape(batch_only).device(d) = backprop.exp().sum(along_class); @@ -110,15 +99,15 @@ struct XentEigenImpl { // sum(-labels * // ((logits - max_logits) - log(sum(exp(logits - max_logits))))) // along classes - loss.device(d) = (labels.broadcast(labels_bcast) * - (scratch.log().eval().broadcast(one_by_class) - backprop)) - .eval() - .sum(along_class); + loss.device(d) = + (labels * (scratch.log().eval().broadcast(one_by_class) - backprop)) + .eval() + .sum(along_class); // backprop: prob - labels, where // prob = exp(logits - max_logits) / sum(exp(logits - max_logits)) - backprop.device(d) = (backprop.exp() / scratch.broadcast(one_by_class)) - - labels.broadcast(labels_bcast); + backprop.device(d) = + (backprop.exp() / scratch.broadcast(one_by_class)) - labels; } }; diff --git a/tensorflow/core/kernels/xent_op_gpu.cu.cc b/tensorflow/core/kernels/xent_op_gpu.cu.cc index 2c0c0b3a02..05ee7da490 100644 --- a/tensorflow/core/kernels/xent_op_gpu.cu.cc +++ b/tensorflow/core/kernels/xent_op_gpu.cu.cc @@ -31,17 +31,12 @@ typedef Eigen::GpuDevice GPUDevice; namespace functor { template struct XentFunctor { - void operator()(const GPUDevice &d, - const Eigen::DSizes &shape, - const Eigen::array &logits_bcast, - const Eigen::array &labels_bcast, - typename TTypes::ConstMatrix logits, + void operator()(const GPUDevice& d, typename TTypes::ConstMatrix logits, typename TTypes::ConstMatrix labels, typename TTypes::Matrix scratch, typename TTypes::Vec loss, typename TTypes::Matrix backprop) { - XentEigenImpl::Compute(d, shape, logits_bcast, labels_bcast, - logits, labels, scratch, loss, + XentEigenImpl::Compute(d, logits, labels, scratch, loss, backprop); } }; diff --git a/tensorflow/core/ops/array_ops.cc b/tensorflow/core/ops/array_ops.cc index 111670c361..88d2aa3f41 100644 --- a/tensorflow/core/ops/array_ops.cc +++ b/tensorflow/core/ops/array_ops.cc @@ -794,35 +794,11 @@ REGISTER_OP("ReverseV2") ShapeHandle input = c->input(0); ShapeHandle axis; TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &axis)); + // TODO(aselle): if input(0)'s dimension is known we could validate axis if (c->Rank(input) > 8) { return errors::InvalidArgument( "reverse does not work on tensors with more than 8 dimensions"); } - const Tensor* axis_tensor = c->input_tensor(1); - if (axis_tensor != nullptr && c->RankKnown(input)) { - int32 rank = c->Rank(input); - std::vector axis_value; - if (axis_tensor->dtype() == DT_INT32) { - axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); - } else { - axis_value = AsInt64(axis_tensor, axis_tensor->NumElements()); - } - std::vector axes_dense(c->Rank(input), false); - for (int i = 0; i < axis_value.size(); i++) { - int64 canonical_axis = - axis_value[i] < 0 ? rank + axis_value[i] : axis_value[i]; - if (canonical_axis < 0 || canonical_axis >= rank) { - return errors::InvalidArgument("'axis'[", i, "] = ", axis_value[i], - " is out of valid range [", 0, ", ", - rank - 1); - } - if (axes_dense[canonical_axis]) { - return errors::InvalidArgument("axis ", canonical_axis, - " specified more than once."); - } - axes_dense[canonical_axis] = true; - } - } c->set_output(0, input); return Status::OK(); }); diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 6c2fc60bab..1f4e9753c3 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1062,27 +1062,12 @@ REGISTER_OP("SoftmaxCrossEntropyWithLogits") .Attr("T: {half, bfloat16, float, double}") .SetShapeFn([](InferenceContext* c) { ShapeHandle input; - if (c->WithRank(c->input(0), 2, &input) == Status::OK() && - c->Merge(input, c->input(1), &input) == Status::OK()) { - DimensionHandle batch_size = c->Dim(input, 0); - c->set_output(0, c->Vector(batch_size)); - c->set_output(1, input); - return Status::OK(); - } - TF_RETURN_IF_ERROR(BroadcastBinaryOpOutputShapeFn(c, 1)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &input)); + TF_RETURN_IF_ERROR(c->Merge(input, c->input(1), &input)); - if (!c->RankKnown(c->output(1))) { - return errors::InvalidArgument( - "Shape must be broadcasted with rank 2, but is rank is unknown."); - } - - if (c->Rank(c->output(1)) != 2) { - return errors::InvalidArgument( - "Shape must be broadcasted with rank 2, but is rank ", - c->Rank(c->output(1))); - } - DimensionHandle batch_size = c->Dim(c->output(1), 0); + DimensionHandle batch_size = c->Dim(input, 0); c->set_output(0, c->Vector(batch_size)); + c->set_output(1, input); return Status::OK(); }); diff --git a/tensorflow/core/ops/nn_ops_test.cc b/tensorflow/core/ops/nn_ops_test.cc index 289b953055..1b17a7cda6 100644 --- a/tensorflow/core/ops/nn_ops_test.cc +++ b/tensorflow/core/ops/nn_ops_test.cc @@ -410,18 +410,10 @@ TEST(NNOpsTest, SoftmaxCrossEntropyWithLogits_ShapeFn) { INFER_OK(op, "[1,?];[?,2]", "[d0_0];[d0_0,d0_1|d1_1]"); INFER_OK(op, "[?,2];[1,2]", "[d1_0];in1"); - INFER_ERROR("Shape must be broadcasted with rank 2", op, "[1,2,3];?"); - INFER_ERROR("Shape must be broadcasted with rank 2", op, "?;[1,2,3]"); - - // Broadcast example - // [1,4] and [2,4] are broadcasted to [2,4] - INFER_OK(op, "[1,4];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); - // [2,4] and [2,1] are broadcasted to [2,4] - INFER_OK(op, "[2,4];[2,1]", "[d0_0];[d0_0|d1_0,d0_1]"); - // [1,?] and [2,4] are broadcasted to [2,4] - INFER_OK(op, "[1,?];[2,4]", "[d1_0];[d1_0,d0_1|d1_1]"); - // [2,4] and [?,1] are broadcasted to [2,4] - INFER_OK(op, "[2,4];[?,1]", "[d0_0];[d0_0|d1_0,d0_1]"); + INFER_ERROR("Dimension 0 in both shapes must be equal, but are 1 and 2", op, + "[1,?];[2,?]"); + INFER_ERROR("Shape must be rank 2 but is rank 3", op, "[1,2,3];?"); + INFER_ERROR("Shapes must be equal rank, but are 2 and 3", op, "?;[1,2,3]"); } TEST(NNOpsTest, SparseSoftmaxCrossEntropyWithLogits_ShapeFn) { diff --git a/tensorflow/core/public/version.h b/tensorflow/core/public/version.h index 40eebd1db0..22f2c02b78 100644 --- a/tensorflow/core/public/version.h +++ b/tensorflow/core/public/version.h @@ -19,12 +19,12 @@ limitations under the License. // TensorFlow uses semantic versioning, see http://semver.org/. #define TF_MAJOR_VERSION 1 -#define TF_MINOR_VERSION 7 +#define TF_MINOR_VERSION 6 #define TF_PATCH_VERSION 0 // TF_VERSION_SUFFIX is non-empty for pre-releases (e.g. "-alpha", "-alpha.1", // "-beta", "-rc", "-rc.1") -#define TF_VERSION_SUFFIX "-rc1" +#define TF_VERSION_SUFFIX "" #define TF_STR_HELPER(x) #x #define TF_STR(x) TF_STR_HELPER(x) diff --git a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md index f3db5857ae..956dccb64f 100644 --- a/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md +++ b/tensorflow/docs_src/api_guides/python/contrib.bayesflow.monte_carlo.md @@ -6,42 +6,42 @@ Monte Carlo integration and helpers. ## Background Monte Carlo integration refers to the practice of estimating an expectation with -a sample mean. For example, given random variable `Z in \\(R^k\\)` with density `p`, +a sample mean. For example, given random variable `Z in R^k` with density `p`, the expectation of function `f` can be approximated like: ``` -$$E_p[f(Z)] = \int f(z) p(z) dz$$ -$$ ~ S_n - := n^{-1} \sum_{i=1}^n f(z_i), z_i\ iid\ samples\ from\ p.$$ +E_p[f(Z)] = \int f(z) p(z) dz + ~ S_n + := n^{-1} \sum_{i=1}^n f(z_i), z_i iid samples from p. ``` -If `\\(E_p[|f(Z)|] < infinity\\)`, then `\\(S_n\\) --> \\(E_p[f(Z)]\\)` by the strong law of large -numbers. If `\\(E_p[f(Z)^2] < infinity\\)`, then `\\(S_n\\)` is asymptotically normal with -variance `\\(Var[f(Z)] / n\\)`. +If `E_p[|f(Z)|] < infinity`, then `S_n --> E_p[f(Z)]` by the strong law of large +numbers. If `E_p[f(Z)^2] < infinity`, then `S_n` is asymptotically normal with +variance `Var[f(Z)] / n`. Practitioners of Bayesian statistics often find themselves wanting to estimate -`\\(E_p[f(Z)]\\)` when the distribution `p` is known only up to a constant. For +`E_p[f(Z)]` when the distribution `p` is known only up to a constant. For example, the joint distribution `p(z, x)` may be known, but the evidence -`\\(p(x) = \int p(z, x) dz\\)` may be intractable. In that case, a parameterized -distribution family `\\(q_\lambda(z)\\)` may be chosen, and the optimal `\\(\lambda\\)` is the -one minimizing the KL divergence between `\\(q_\lambda(z)\\)` and -`\\(p(z | x)\\)`. We only know `p(z, x)`, but that is sufficient to find `\\(\lambda\\)`. +`p(x) = \int p(z, x) dz` may be intractable. In that case, a parameterized +distribution family `q_lambda(z)` may be chosen, and the optimal `lambda` is the +one minimizing the KL divergence between `q_lambda(z)` and +`p(z | x)`. We only know `p(z, x)`, but that is sufficient to find `lambda`. ## Log-space evaluation and subtracting the maximum Care must be taken when the random variable lives in a high dimensional space. -For example, the naive importance sample estimate `\\(E_q[f(Z) p(Z) / q(Z)]\\)` -involves the ratio of two terms `\\(p(Z) / q(Z)\\)`, each of which must have tails -dropping off faster than `\\(O(|z|^{-(k + 1)})\\)` in order to have finite integral. +For example, the naive importance sample estimate `E_q[f(Z) p(Z) / q(Z)]` +involves the ratio of two terms `p(Z) / q(Z)`, each of which must have tails +dropping off faster than `O(|z|^{-(k + 1)})` in order to have finite integral. This ratio would often be zero or infinity up to numerical precision. For that reason, we write ``` -$$Log E_q[ f(Z) p(Z) / q(Z) ]$$ -$$ = Log E_q[ \exp\{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C\} ] + C,$$ where -$$C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ].$$ +Log E_q[ f(Z) p(Z) / q(Z) ] + = Log E_q[ exp{Log[f(Z)] + Log[p(Z)] - Log[q(Z)] - C} ] + C, where +C := Max[ Log[f(Z)] + Log[p(Z)] - Log[q(Z)] ]. ``` The maximum value of the exponentiated term will be 0.0, and the expectation diff --git a/tensorflow/docs_src/api_guides/python/contrib.losses.md b/tensorflow/docs_src/api_guides/python/contrib.losses.md index 8b7442216c..d7f862625e 100644 --- a/tensorflow/docs_src/api_guides/python/contrib.losses.md +++ b/tensorflow/docs_src/api_guides/python/contrib.losses.md @@ -107,19 +107,19 @@ weighted average over the individual prediction errors: loss = tf.contrib.losses.mean_squared_error(predictions, depths, weight) ``` -* @{tf.contrib.losses.absolute_difference} -* @{tf.contrib.losses.add_loss} -* @{tf.contrib.losses.hinge_loss} -* @{tf.contrib.losses.compute_weighted_loss} -* @{tf.contrib.losses.cosine_distance} -* @{tf.contrib.losses.get_losses} -* @{tf.contrib.losses.get_regularization_losses} -* @{tf.contrib.losses.get_total_loss} -* @{tf.contrib.losses.log_loss} -* @{tf.contrib.losses.mean_pairwise_squared_error} -* @{tf.contrib.losses.mean_squared_error} -* @{tf.contrib.losses.sigmoid_cross_entropy} -* @{tf.contrib.losses.softmax_cross_entropy} -* @{tf.contrib.losses.sparse_softmax_cross_entropy} +@{tf.contrib.losses.absolute_difference} +@{tf.contrib.losses.add_loss} +@{tf.contrib.losses.hinge_loss} +@{tf.contrib.losses.compute_weighted_loss} +@{tf.contrib.losses.cosine_distance} +@{tf.contrib.losses.get_losses} +@{tf.contrib.losses.get_regularization_losses} +@{tf.contrib.losses.get_total_loss} +@{tf.contrib.losses.log_loss} +@{tf.contrib.losses.mean_pairwise_squared_error} +@{tf.contrib.losses.mean_squared_error} +@{tf.contrib.losses.sigmoid_cross_entropy} +@{tf.contrib.losses.softmax_cross_entropy} +@{tf.contrib.losses.sparse_softmax_cross_entropy} diff --git a/tensorflow/docs_src/community/documentation.md b/tensorflow/docs_src/community/documentation.md index 6f2107ef40..003e0a25ec 100644 --- a/tensorflow/docs_src/community/documentation.md +++ b/tensorflow/docs_src/community/documentation.md @@ -477,29 +477,31 @@ should use Markdown in the docstring. Here's a simple example: - def foo(x, y, name="bar"): - """Computes foo. +```python +def foo(x, y, name="bar"): + """Computes foo. - Given two 1-D tensors `x` and `y`, this operation computes the foo. + Given two 1-D tensors `x` and `y`, this operation computes the foo. - Example: + Example: - ``` - # x is [1, 1] - # y is [2, 2] - tf.foo(x, y) ==> [3, 3] - ``` - Args: - x: A `Tensor` of type `int32`. - y: A `Tensor` of type `int32`. - name: A name for the operation (optional). + ``` + # x is [1, 1] + # y is [2, 2] + tf.foo(x, y) ==> [3, 3] + ``` + Args: + x: A `Tensor` of type `int32`. + y: A `Tensor` of type `int32`. + name: A name for the operation (optional). - Returns: - A `Tensor` of type `int32` that is the foo of `x` and `y`. + Returns: + A `Tensor` of type `int32` that is the foo of `x` and `y`. - Raises: - ValueError: If `x` or `y` are not of type `int32`. - """ + Raises: + ValueError: If `x` or `y` are not of type `int32`. + """ +``` ## Description of the docstring sections diff --git a/tensorflow/docs_src/install/install_c.md b/tensorflow/docs_src/install/install_c.md index 9059b3f3b6..0481c97885 100644 --- a/tensorflow/docs_src/install/install_c.md +++ b/tensorflow/docs_src/install/install_c.md @@ -38,7 +38,7 @@ enable TensorFlow for C: OS="linux" # Change to "darwin" for macOS TARGET_DIRECTORY="/usr/local" curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_go.md b/tensorflow/docs_src/install/install_go.md index 2e47a6d212..8f89898c92 100644 --- a/tensorflow/docs_src/install/install_go.md +++ b/tensorflow/docs_src/install/install_go.md @@ -38,7 +38,7 @@ steps to install this library and enable TensorFlow for Go: TF_TYPE="cpu" # Change to "gpu" for GPU support TARGET_DIRECTORY='/usr/local' curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.7.0-rc1.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-$(go env GOOS)-x86_64-1.6.0.tar.gz" | sudo tar -C $TARGET_DIRECTORY -xz The `tar` command extracts the TensorFlow C library into the `lib` diff --git a/tensorflow/docs_src/install/install_java.md b/tensorflow/docs_src/install/install_java.md index eff066d200..0ee9c849e1 100644 --- a/tensorflow/docs_src/install/install_java.md +++ b/tensorflow/docs_src/install/install_java.md @@ -36,7 +36,7 @@ following to the project's `pom.xml` to use the TensorFlow Java APIs: org.tensorflow tensorflow - 1.7.0-rc1 + 1.6.0 ``` @@ -65,7 +65,7 @@ As an example, these steps will create a Maven project that uses TensorFlow: org.tensorflow tensorflow - 1.7.0-rc1 + 1.6.0 @@ -123,12 +123,12 @@ instead: org.tensorflow libtensorflow - 1.7.0-rc1 + 1.6.0 org.tensorflow libtensorflow_jni_gpu - 1.7.0-rc1 + 1.6.0 ``` @@ -147,7 +147,7 @@ refer to the simpler instructions above instead. Take the following steps to install TensorFlow for Java on Linux or macOS: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), which is the TensorFlow Java Archive (JAR). 2. Decide whether you will run TensorFlow for Java on CPU(s) only or with @@ -166,7 +166,7 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: OS=$(uname -s | tr '[:upper:]' '[:lower:]') mkdir -p ./jni curl -L \ - "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.7.0-rc1.tar.gz" | + "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-${TF_TYPE}-${OS}-x86_64-1.6.0.tar.gz" | tar -xz -C ./jni ### Install on Windows @@ -174,10 +174,10 @@ Take the following steps to install TensorFlow for Java on Linux or macOS: Take the following steps to install TensorFlow for Java on Windows: 1. Download - [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.7.0-rc1.jar), + [libtensorflow.jar](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-1.6.0.jar), which is the TensorFlow Java Archive (JAR). 2. Download the following Java Native Interface (JNI) file appropriate for - [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.7.0-rc1.zip). + [TensorFlow for Java on Windows](https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow_jni-cpu-windows-x86_64-1.6.0.zip). 3. Extract this .zip file. @@ -225,7 +225,7 @@ must be part of your `classpath`. For example, you can include the downloaded `.jar` in your `classpath` by using the `-cp` compilation flag as follows: -
javac -cp libtensorflow-1.7.0-rc1.jar HelloTF.java
+
javac -cp libtensorflow-1.6.0.jar HelloTF.java
### Running @@ -239,11 +239,11 @@ two files are available to the JVM: For example, the following command line executes the `HelloTF` program on Linux and macOS X: -
java -cp libtensorflow-1.7.0-rc1.jar:. -Djava.library.path=./jni HelloTF
+
java -cp libtensorflow-1.6.0.jar:. -Djava.library.path=./jni HelloTF
And the following command line executes the `HelloTF` program on Windows: -
java -cp libtensorflow-1.7.0-rc1.jar;. -Djava.library.path=jni HelloTF
+
java -cp libtensorflow-1.6.0.jar;. -Djava.library.path=jni HelloTF
If the program prints Hello from version, you've successfully installed TensorFlow for Java and are ready to use the API. If the program diff --git a/tensorflow/docs_src/install/install_linux.md b/tensorflow/docs_src/install/install_linux.md index 27b696696d..5e9a84bff6 100644 --- a/tensorflow/docs_src/install/install_linux.md +++ b/tensorflow/docs_src/install/install_linux.md @@ -165,7 +165,7 @@ Take the following steps to install TensorFlow with Virtualenv: Virtualenv environment:
(tensorflow)$ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl If you encounter installation problems, see [Common Installation Problems](#common_installation_problems). @@ -270,7 +270,7 @@ take the following steps:
      $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
      
If this step fails, see @@ -456,7 +456,7 @@ Take the following steps to install TensorFlow in an Anaconda environment:
      (tensorflow)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+ https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl ## Validate your installation @@ -630,14 +630,14 @@ This section documents the relevant values for Linux installations. CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp27-none-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp27-none-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp27-none-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -649,14 +649,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp34-cp34m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp34-cp34m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp34-cp34m-linux_x86_64.whl
 
Note that GPU support requires the NVIDIA hardware and software described in @@ -668,14 +668,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp35-cp35m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp35-cp35m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp35-cp35m-linux_x86_64.whl
 
@@ -687,14 +687,14 @@ Note that GPU support requires the NVIDIA hardware and software described in CPU only:
-https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.6.0-cp36-cp36m-linux_x86_64.whl
 
GPU support:
-https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.7.0rc1-cp36-cp36m-linux_x86_64.whl
+https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.6.0-cp36-cp36m-linux_x86_64.whl
 
diff --git a/tensorflow/docs_src/install/install_mac.md b/tensorflow/docs_src/install/install_mac.md index 7060ef43da..55b460e189 100644 --- a/tensorflow/docs_src/install/install_mac.md +++ b/tensorflow/docs_src/install/install_mac.md @@ -118,8 +118,8 @@ Take the following steps to install TensorFlow with Virtualenv: Python 2.7, the command to install TensorFlow in the active Virtualenv is as follows: -
 $ pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
+
 $ pip install --upgrade \
+     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
If you encounter installation problems, see [Common Installation Problems](#common-installation-problems). @@ -241,8 +241,8 @@ take the following steps: you are installing TensorFlow for macOS and Python 2.7 issue the following command: -
 $ sudo pip3 install --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl 
+
 $ sudo pip install --upgrade \
+     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl 
If the preceding command fails, see [installation problems](#common-installation-problems). @@ -350,7 +350,7 @@ Take the following steps to install TensorFlow in an Anaconda environment: TensorFlow for Python 2.7:
 (targetDirectory)$ pip install --ignore-installed --upgrade \
-     https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
+ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl @@ -524,7 +524,7 @@ The value you specify depends on your Python version.
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py2-none-any.whl
 
@@ -532,5 +532,5 @@ https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py2-none-a
-https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.7.0rc1-py3-none-any.whl
+https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.6.0-py3-none-any.whl
 
diff --git a/tensorflow/docs_src/install/install_sources.md b/tensorflow/docs_src/install/install_sources.md index 148f80efe2..a7f33819b4 100644 --- a/tensorflow/docs_src/install/install_sources.md +++ b/tensorflow/docs_src/install/install_sources.md @@ -350,10 +350,10 @@ Invoke `pip install` to install that pip package. The filename of the `.whl` file depends on your platform. For example, the following command will install the pip package -for TensorFlow 1.7.0rc1 on Linux: +for TensorFlow 1.6.0 on Linux:
-$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.7.0rc1-py2-none-any.whl
+$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.6.0-py2-none-any.whl
 
## Validate your installation @@ -450,8 +450,6 @@ Stack Overflow and specify the `tensorflow` tag. **Linux**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc1GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
- - @@ -471,7 +469,6 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc1GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
- @@ -486,8 +483,6 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
- - diff --git a/tensorflow/docs_src/mobile/optimizing.md b/tensorflow/docs_src/mobile/optimizing.md index 778e4d3a62..ca9cb043e9 100644 --- a/tensorflow/docs_src/mobile/optimizing.md +++ b/tensorflow/docs_src/mobile/optimizing.md @@ -233,8 +233,6 @@ order by how long they took. From left to right, the columns are: - The cumulative total time of this and the previous ops in the table. This is handy for understanding what the distribution of work is across the layers, to see if just a few of the nodes are taking up most of the time. - -- The amount of memory consumed by outputs of this type of op. - Name of the node. diff --git a/tensorflow/docs_src/mobile/prepare_models.md b/tensorflow/docs_src/mobile/prepare_models.md index 8b22c04d87..360ee302aa 100644 --- a/tensorflow/docs_src/mobile/prepare_models.md +++ b/tensorflow/docs_src/mobile/prepare_models.md @@ -60,7 +60,7 @@ and serialized as protocol buffers: the `NodeDef`, so if all the `Variable` weights are converted to `Const` nodes, then we only need a single `GraphDef` file to hold the model architecture and the weights. Freezing the graph handles the process of loading the - checkpoints, and then converts all Variables to Consts. You can then load the + checkpoints, and then converts all Consts to Variables. You can then load the resulting file in a single call, without having to restore variable values from checkpoints. One thing to watch out for with `GraphDef` files is that sometimes they’re stored in text format for easy inspection. These versions diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index a0dd409205..4f61c01f65 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -1065,7 +1065,7 @@ py_test( py_test( name = "framework_importer_test", - size = "large", + size = "medium", srcs = ["framework/importer_test.py"], main = "framework/importer_test.py", srcs_version = "PY2AND3", diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index 64c1760d5e..d0ba8020c1 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -315,39 +315,21 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf_4, np.asarray(x_np)[:, ::-1]) self.assertAllEqual(x_tf_5, np.asarray(x_np)[::-1, ::-1]) - # This test covers the axis validation in the shape function - # (no eval()) - def testInvalidAxis(self): - x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) - with self.assertRaisesRegexp(ValueError, - "is out of valid range"): - array_ops.reverse_v2(x_np, [-30]) - with self.assertRaisesRegexp(ValueError, - "is out of valid range"): - array_ops.reverse_v2(x_np, [2]) - with self.assertRaisesRegexp(ValueError, - "axis 0 specified more than once"): - array_ops.reverse_v2(x_np, [0, -2]) - # This is the version of reverse that uses axis indices rather than # bool tensors # TODO(b/32254538): Change this test to use array_ops.reverse - # - # Note: this test passes placeholder as constant axis is validated - # in shape function (see testInvalidAxis) def testInvalid(self): x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) - axis = array_ops.placeholder(dtypes.int32) with self.test_session(): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [-30]}) + array_ops.reverse_v2(x_np, [-30]).eval() with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [2]}) + array_ops.reverse_v2(x_np, [2]).eval() with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "axis 0 specified more than once"): - array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [0, -2]}) + array_ops.reverse_v2(x_np, [0, -2]).eval() def testReverse1DimAuto(self): for dtype in [ @@ -908,7 +890,7 @@ class StridedSliceAssignChecker(object): var = resource_variable_ops.ResourceVariable(self.x) else: var = variables.Variable(self.x) - sess.run(variables.variables_initializer([var])) + sess.run(variables.initialize_variables([var])) val = sess.run(var[index].assign(value)) # val_copy is used to check that tf.assign works equivalently to the # assign method above. diff --git a/tensorflow/python/kernel_tests/testdata/BUILD b/tensorflow/python/kernel_tests/testdata/BUILD index 45264c773a..a4a0dfc139 100644 --- a/tensorflow/python/kernel_tests/testdata/BUILD +++ b/tensorflow/python/kernel_tests/testdata/BUILD @@ -1,7 +1,7 @@ # Data files for kernel tests. package( - default_visibility = ["//visibility:public"], + default_visibility = ["//tensorflow:internal"], ) licenses(["notice"]) # Apache 2.0 diff --git a/tensorflow/python/kernel_tests/xent_op_test.py b/tensorflow/python/kernel_tests/xent_op_test.py index 60c726d54c..e3e120a4eb 100644 --- a/tensorflow/python/kernel_tests/xent_op_test.py +++ b/tensorflow/python/kernel_tests/xent_op_test.py @@ -18,16 +18,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import itertools -import sys - import numpy as np -from tensorflow.python.client import session from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl @@ -94,7 +88,7 @@ class XentTest(test.TestCase): 4.]]]).astype(dtype) np_labels = np.array([[[0., 0., 0., 1.]], [[0., .5, .5, 0.]]]).astype(dtype) - self.assertRaisesRegexp(ValueError, "rank 2, but is rank 3", + self.assertRaisesRegexp(ValueError, "must be rank 2", gen_nn_ops.softmax_cross_entropy_with_logits, np_features, np_labels) @@ -134,24 +128,6 @@ class XentTest(test.TestCase): self.assertAllClose( np.array([1.3862, 1.9401]), np_loss, rtol=1.e-3, atol=1.e-3) - def testShapeBroadcast(self): - np_f = np.array([[1., 2., 3., 4.], - [1., 2., 3., 4.]]).astype(np.float32) - np_l = np.array([[0., 0., 0., 1.], - [0., .5, .5, 0.]]).astype(np.float32) - np_loss, np_backprop = self._npXent(np_f, np_l) - tf_f = constant_op.constant( - np.array([[1., 2., 3., 4.]]).astype(np.float32)) - tf_l = constant_op.constant( - np.array([[0., 0., 0., 1.], [0., .5, .5, 0.]]).astype(np.float32)) - for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu) as sess: - loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( - tf_f, tf_l) - tf_loss, tf_backprop = sess.run([loss, backprop]) - self.assertAllCloseAccordingToType(np_loss, tf_loss) - self.assertAllCloseAccordingToType(np_backprop, tf_backprop) - def testShapeMismatch(self): with self.test_session(): with self.assertRaises(ValueError): @@ -284,60 +260,5 @@ class XentTest(test.TestCase): self.assertAllEqual(np_loss, tf_loss) -class XentBenchmark(test.Benchmark): - - def benchmarkZeroDimension(self): - for (m, n, p, use_gpu) in itertools.product( - [128], - [10, 100, 1000, 10000, 100000], - [0.001, 0.01, 0.5, 0.99, 1.0], - [False]): - k = int(p * n) - if k == 0: - continue - name = "zero_dimension_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) - device = "/%s:0" % ("gpu" if use_gpu else "cpu") - with ops.Graph().as_default(): - with ops.device(device): - labels = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) - logits = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) - op = nn_ops.softmax_cross_entropy_with_logits( - labels=labels, logits=logits) - with session.Session() as sess: - r = self.run_op_benchmark(sess, op, min_iters=100, name=name) - gb_processed_input = m * n / 1.0e9 - throughput = gb_processed_input / r["wall_time"] - print("Benchmark: %s \t wall_time: %0.03g s \t " - "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) - sys.stdout.flush() - - def benchmarkSingleClass(self): - for (m, n, p, use_gpu) in itertools.product( - [128], - [10, 100, 1000, 10000, 100000], - [0.001, 0.01, 0.5, 0.99, 1.0], - [False]): - k = int(p * n) - if k == 0: - continue - name = "single_class_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) - device = "/%s:0" % ("gpu" if use_gpu else "cpu") - with ops.Graph().as_default(): - with ops.device(device): - labels = constant_op.constant([[1.], [-1.], [0.]], - dtype=dtypes.float32) - logits = constant_op.constant([[-1.], [0.], [1.]], - dtype=dtypes.float32) - op = nn_ops.softmax_cross_entropy_with_logits( - labels=labels, logits=logits) - with session.Session() as sess: - r = self.run_op_benchmark(sess, op, min_iters=100, name=name) - gb_processed_input = m * n / 1.0e9 - throughput = gb_processed_input / r["wall_time"] - print("Benchmark: %s \t wall_time: %0.03g s \t " - "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) - sys.stdout.flush() - - if __name__ == "__main__": test.main() diff --git a/tensorflow/python/layers/convolutional.py b/tensorflow/python/layers/convolutional.py index 2d99b1688f..74e7c63fb3 100644 --- a/tensorflow/python/layers/convolutional.py +++ b/tensorflow/python/layers/convolutional.py @@ -180,8 +180,6 @@ class _Conv(base.Layer): # bias_add when computing gradients. To use bias_add, we collapse Z # and Y into a single dimension to obtain a 4D input tensor. outputs_shape = outputs.shape.as_list() - if outputs_shape[0] is None: - outputs_shape[0] = -1 outputs_4d = array_ops.reshape(outputs, [outputs_shape[0], outputs_shape[1], outputs_shape[2] * outputs_shape[3], diff --git a/tensorflow/python/layers/convolutional_test.py b/tensorflow/python/layers/convolutional_test.py index cdb42f5bd1..160e732b67 100644 --- a/tensorflow/python/layers/convolutional_test.py +++ b/tensorflow/python/layers/convolutional_test.py @@ -325,12 +325,6 @@ class ConvTest(test.TestCase): self.assertEqual(conv3d.kernel_constraint, k_constraint) self.assertEqual(conv3d.bias_constraint, b_constraint) - def testConv3DChannelsFirst(self): - # Test case for GitHub issue 15655 - images = array_ops.placeholder( - dtype=dtypes.float32, shape=[None, 1, 32, 32, 32]) - conv_layers.conv3d(images, 32, 9, data_format='channels_first') - @test_util.with_c_api class SeparableConv1DTest(test.TestCase): diff --git a/tensorflow/python/ops/linalg_ops.py b/tensorflow/python/ops/linalg_ops.py index 170861b43f..5b4fb4f7c8 100644 --- a/tensorflow/python/ops/linalg_ops.py +++ b/tensorflow/python/ops/linalg_ops.py @@ -429,7 +429,7 @@ def svd(tensor, full_matrices=False, compute_uv=True, name=None): u, s, v_adj = np.linalg.svd(a, full_matrices=False) np_a_approx = np.dot(u, np.dot(np.diag(s), v_adj)) # tf_a_approx and np_a_approx should be numerically close. - ``` + ```` @end_compatibility """ s, u, v = gen_linalg_ops.svd( diff --git a/tensorflow/python/training/monitored_session.py b/tensorflow/python/training/monitored_session.py index 4ce6f6d002..6c5c9e01a7 100644 --- a/tensorflow/python/training/monitored_session.py +++ b/tensorflow/python/training/monitored_session.py @@ -281,14 +281,13 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name scaffold=None, hooks=None, chief_only_hooks=None, - save_checkpoint_secs=USE_DEFAULT, + save_checkpoint_secs=600, save_summaries_steps=USE_DEFAULT, save_summaries_secs=USE_DEFAULT, config=None, stop_grace_period_secs=120, log_step_count_steps=100, - max_wait_secs=7200, - save_checkpoint_steps=USE_DEFAULT): + max_wait_secs=7200): """Creates a `MonitoredSession` for training. For a chief, this utility sets proper session initializer/restorer. It also @@ -311,10 +310,8 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name chief_only_hooks: list of `SessionRunHook` objects. Activate these hooks if `is_chief==True`, ignore otherwise. save_checkpoint_secs: The frequency, in seconds, that a checkpoint is saved - using a default checkpoint saver. If both `save_checkpoint_steps` and - `save_checkpoint_secs` are set to `None`, then the default checkpoint - saver isn't used. If both are provided, then only `save_checkpoint_secs` - is used. Default 600. + using a default checkpoint saver. If `save_checkpoint_secs` is set to + `None`, then the default checkpoint saver isn't used. save_summaries_steps: The frequency, in number of global steps, that the summaries are written to disk using a default summary saver. If both `save_summaries_steps` and `save_summaries_secs` are set to `None`, then @@ -333,11 +330,6 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name become available. This should be kept relatively short to help detect incorrect code, but sometimes may need to be increased if the chief takes a while to start up. - save_checkpoint_steps: The frequency, in number of global steps, that a - checkpoint is saved using a default checkpoint saver. If both - `save_checkpoint_steps` and `save_checkpoint_secs` are set to `None`, then - the default checkpoint saver isn't used. If both are provided, then only - `save_checkpoint_secs` is used. Default not enabled. Returns: A `MonitoredSession` object. @@ -350,15 +342,6 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name elif save_summaries_steps == USE_DEFAULT: save_summaries_steps = None - if (save_checkpoint_steps == USE_DEFAULT and - save_checkpoint_secs == USE_DEFAULT): - save_checkpoint_steps = None - save_checkpoint_secs = 600 - elif save_checkpoint_secs == USE_DEFAULT: - save_checkpoint_secs = None - elif save_checkpoint_steps == USE_DEFAULT: - save_checkpoint_steps = None - scaffold = scaffold or Scaffold() if not is_chief: session_creator = WorkerSessionCreator( @@ -391,13 +374,9 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name save_steps=save_summaries_steps, save_secs=save_summaries_secs, output_dir=checkpoint_dir)) - if (save_checkpoint_secs and save_checkpoint_secs > 0) or ( - save_checkpoint_steps and save_checkpoint_steps > 0): + if save_checkpoint_secs and save_checkpoint_secs > 0: all_hooks.append(basic_session_run_hooks.CheckpointSaverHook( - checkpoint_dir, - save_steps=save_checkpoint_steps, - save_secs=save_checkpoint_secs, - scaffold=scaffold)) + checkpoint_dir, save_secs=save_checkpoint_secs, scaffold=scaffold)) if hooks: all_hooks.extend(hooks) diff --git a/tensorflow/python/training/monitored_session_test.py b/tensorflow/python/training/monitored_session_test.py index 3806056f01..159b2d5c16 100644 --- a/tensorflow/python/training/monitored_session_test.py +++ b/tensorflow/python/training/monitored_session_test.py @@ -282,42 +282,6 @@ class MonitoredTrainingSessionTest(test.TestCase): is_chief=True, checkpoint_dir=logdir) as session: self.assertEqual(2, session.run(gstep)) - def test_save_checkpoint_steps(self): - logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_steps') - with ops.Graph().as_default(): - gstep = variables_lib.get_or_create_global_step() - new_gstep = state_ops.assign_add(gstep, 1) - with monitored_session.MonitoredTrainingSession( - is_chief=True, - checkpoint_dir=logdir, - save_checkpoint_steps=100, - log_step_count_steps=10) as session: - for _ in range(100): - session.run(new_gstep) - # A restart will find the checkpoint and recover automatically. - with monitored_session.MonitoredTrainingSession( - is_chief=True, checkpoint_dir=logdir) as session: - self.assertEqual(100, session.run(gstep)) - - def test_save_checkpoint_secs(self): - logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_secs') - with ops.Graph().as_default(): - gstep = variables_lib.get_or_create_global_step() - new_gstep = state_ops.assign_add(gstep, 1) - with monitored_session.MonitoredTrainingSession( - is_chief=True, - checkpoint_dir=logdir, - save_checkpoint_secs=0.1, - log_step_count_steps=10) as session: - session.run(new_gstep) - time.sleep(0.2) - for _ in range(10): - session.run(new_gstep) - # A restart will find the checkpoint and recover automatically. - with monitored_session.MonitoredTrainingSession( - is_chief=True, checkpoint_dir=logdir) as session: - self.assertEqual(11, session.run(gstep)) - def test_summaries_steps(self): logdir = _test_dir(self.get_temp_dir(), 'test_summaries_steps') with ops.Graph().as_default(): diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index fcc57d506e..2d3cb415fe 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -22,7 +22,6 @@ load( load( "//third_party/mkl:build_defs.bzl", "if_mkl", - "if_mkl_lnx_x64" ) def register_extension_info(**kwargs): @@ -203,8 +202,7 @@ def tf_copts(android_optimization_level_override="-O2", is_external=False): "-ftemplate-depth=900"]) + if_cuda(["-DGOOGLE_CUDA=1"]) + if_tensorrt(["-DGOOGLE_TENSORRT=1"]) - + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML"]) - + if_mkl_lnx_x64(["-fopenmp"]) + + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML", "-fopenmp",]) + if_android_arm(["-mfpu=neon"]) + if_linux_x86_64(["-msse3"]) + if_ios_x86_64(["-msse4.1"]) diff --git a/tensorflow/tools/api/golden/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/tensorflow.train.pbtxt index bec72e1e60..c75ee474aa 100644 --- a/tensorflow/tools/api/golden/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.train.pbtxt @@ -238,7 +238,7 @@ tf_module { } member_method { name: "MonitoredTrainingSession" - argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\', \'save_checkpoint_steps\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\', \'\'], " + argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'600\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\'], " } member_method { name: "NewCheckpointReader" diff --git a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh index 7d471b4703..e1b56b9a25 100755 --- a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh +++ b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh @@ -31,5 +31,5 @@ export TF_NEED_OPENCL_SYCL=0 export TF_NEED_MKL=0 export COMPUTECPP_PATH="/usr/local" -export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" build_libtensorflow_tarball "-cpu-darwin-$(uname -m)" diff --git a/tensorflow/tools/docker/Dockerfile.devel b/tensorflow/tools/docker/Dockerfile.devel index 11f476d12c..22c73c3fe1 100644 --- a/tensorflow/tools/docker/Dockerfile.devel +++ b/tensorflow/tools/docker/Dockerfile.devel @@ -70,7 +70,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . # TODO(craigcitro): Don't install the pip package, since it makes it # more difficult to experiment with local changes. Instead, just add diff --git a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl index 037d13116e..3690e7dfe5 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl +++ b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl @@ -3,7 +3,7 @@ FROM tensorflow/tensorflow:latest-devel LABEL maintainer="Clayne Robison" # These arguments are parameterized. Use --build-args to override. -ARG TF_BRANCH=r1.7 +ARG TF_BRANCH=r1.6 ARG WHL_DIR=/whl RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/tensorflow/tools/docker/Dockerfile.devel-gpu b/tensorflow/tools/docker/Dockerfile.devel-gpu index 1fcb6428b2..69ba340f92 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-gpu +++ b/tensorflow/tools/docker/Dockerfile.devel-gpu @@ -79,7 +79,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 0ede8c6370..3fbdb5cacd 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -138,6 +138,7 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", + "@mkl//:LICENSE", ]), outs = ["include/tensorflow/c/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", @@ -175,6 +176,7 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", + "@mkl//:LICENSE", ]), outs = ["include/tensorflow/jni/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index 62fec2c402..dd75eda231 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -127,6 +127,7 @@ filegroup( "@org_python_pypi_backports_weakref//:LICENSE", ] + if_mkl([ "//third_party/mkl:LICENSE", + "@mkl//:LICENSE", ]) + if_not_windows([ "@nccl_archive//:LICENSE.txt", ]) + tf_additional_license_deps(), diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 365e8d6b08..e0152da4df 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.7.0-rc1' +_VERSION = '1.6.0' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', @@ -39,7 +39,7 @@ REQUIRED_PACKAGES = [ 'numpy >= 1.13.3', 'six >= 1.10.0', 'protobuf >= 3.4.0', - 'tensorboard >= 1.7.0, < 1.8.0', + 'tensorboard >= 1.6.0, < 1.7.0', 'termcolor >= 1.1.0', ] @@ -62,7 +62,7 @@ else: if 'tf_nightly' in project_name: for i, pkg in enumerate(REQUIRED_PACKAGES): if 'tensorboard' in pkg: - REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.8.0a0, < 1.9.0a0' + REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.7.0a0, < 1.8.0a0' break # weakref.finalize and enum were introduced in Python 3.4 diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 5f6e717532..9fcbfb664b 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -15,11 +15,6 @@ load("@io_bazel_rules_closure//closure/private:java_import_external.bzl", "java_ load("@io_bazel_rules_closure//closure:defs.bzl", "filegroup_external") -# Sanitize a dependency so that it works correctly from code that includes -# TensorFlow as a submodule. -def clean_dep(dep): - return str(Label(dep)) - # If TensorFlow is linked as a submodule. # path_prefix is no longer used. # tf_repo_name is thought to be under consideration. @@ -37,37 +32,17 @@ def tf_workspace(path_prefix="", tf_repo_name=""): arm_compiler_configure( name="local_config_arm_compiler", remote_config_repo="../arm_compiler", - build_file = clean_dep("//third_party/toolchains/cpus/arm:BUILD")) + build_file = str(Label("//third_party/toolchains/cpus/arm:BUILD"))) mkl_repository( - name = "mkl_linux", - urls = [ - "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", - "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", - ], - sha256 = "feacc3d82565c1231470359b42c696236fae873704e0b013436afba5fd4fd30f", - strip_prefix = "mklml_lnx_2018.0.1.20171227", - build_file = clean_dep("//third_party/mkl:mkl.BUILD") - ) - mkl_repository( - name = "mkl_windows", - urls = [ - "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip", - "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip" - ], - sha256 = "24bae8d7b22b431a654acadea43f2243c46ae6b1e5a73a4a936825f31d284ee4", - strip_prefix = "mklml_win_2018.0.1.20171227", - build_file = clean_dep("//third_party/mkl:mkl.BUILD") - ) - mkl_repository( - name = "mkl_darwin", + name = "mkl", urls = [ - "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz", - "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz" + "https://mirror.bazel.build/github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", + "https://github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", ], - sha256 = "0e954ec6fd3dc5e37f64c4043f6b5613dd687558da3df1028b3b7c29ff5cf77f", - strip_prefix = "mklml_mac_2018.0.1.20171227", - build_file = clean_dep("//third_party/mkl:mkl.BUILD") + sha256 = "6b07cb7e5451db67c2e31e785ae458b18f7f363c60a61685488f69e9ae7199d4", + strip_prefix = "mklml_lnx_2018.0.1.20171007", + build_file = str(Label("//third_party/mkl:mkl.BUILD")), ) if path_prefix: @@ -77,12 +52,12 @@ def tf_workspace(path_prefix="", tf_repo_name=""): tf_http_archive( name = "mkl_dnn", urls = [ - "https://mirror.bazel.build/github.com/intel/mkl-dnn/archive/v0.12.tar.gz", - "https://github.com/intel/mkl-dnn/archive/v0.12.tar.gz", + "https://mirror.bazel.build/github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", + "https://github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", ], - sha256 = "86fa2a8c12a56e3b725945acedeaa82492746be02545aba6d710f097e013e19e", - strip_prefix = "mkl-dnn-0.12", - build_file = clean_dep("//third_party/mkl_dnn:mkldnn.BUILD"), + sha256 = "02e244f63dd95402691a361392504c143eede9a89043426f174836638a9cbf09", + strip_prefix = "mkl-dnn-e0bfcaa7fcb2b1e1558f5f0676933c1db807a729", + build_file = str(Label("//third_party/mkl_dnn:mkldnn.BUILD")), ) tf_http_archive( @@ -93,7 +68,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "5996380e3e8b981f55d1c8d58e709c00dbb4806ba367be75d0925a68cc2f6478", strip_prefix = "abseil-cpp-720c017e30339fd1786ce4aac68bc8559736e53f", - build_file = clean_dep("//third_party:com_google_absl.BUILD"), + build_file = str(Label("//third_party:com_google_absl.BUILD")), ) tf_http_archive( @@ -104,8 +79,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "0cadb31a35b514bf2dfd6b5d38205da94ef326ec6908fc3fd7c269948467214f", strip_prefix = "eigen-eigen-2355b229ea4c", - build_file = clean_dep("//third_party:eigen.BUILD"), - patch_file = clean_dep("//third_party:eigen_fix_cuda_compilation.patch") + build_file = str(Label("//third_party:eigen.BUILD")), + patch_file = str(Label("//third_party:eigen_fix_cuda_compilation.patch")) ) tf_http_archive( @@ -118,7 +93,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # remove the whitelist entry in third_party/repo.bzl. # "https://github.com/raspberrypi/tools/archive/0e906ebc527eab1cdbf7adabff5b474da9562e9f.tar.gz", ], - build_file = clean_dep("//:arm_compiler.BUILD"), + build_file = str(Label("//:arm_compiler.BUILD")), ) tf_http_archive( @@ -129,7 +104,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ade869c3f42f23b5263c7d594aa3c7e5e61ac6a3afcaf5d6e42899d2a7986ce", strip_prefix = "libxsmm-1.8.1", - build_file = clean_dep("//third_party:libxsmm.BUILD"), + build_file = str(Label("//third_party:libxsmm.BUILD")), ) tf_http_archive( @@ -142,7 +117,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "932075525642b04ac6f1b50589f1df5cd72ec2f448b721fd32234cf183f0e755", strip_prefix = "or-tools-253f7955c6a1fd805408fba2e42ac6d45b312d15/src", - build_file = clean_dep("//third_party:ortools.BUILD"), + build_file = str(Label("//third_party:ortools.BUILD")), ) tf_http_archive( @@ -174,7 +149,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6560547c63e4af82b0f202cb710ceabb3f21347a4b996db565a411da5b17aba0", strip_prefix = "farmhash-816a4ae622e964763ca0862d9dbd19324a1eaf45", - build_file = clean_dep("//third_party:farmhash.BUILD"), + build_file = str(Label("//third_party:farmhash.BUILD")), ) tf_http_archive( @@ -185,7 +160,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "0f30a15b1566d93f146c8d149878a06e91d9bb7ec2cfd76906df62a82be4aac9", strip_prefix = "highwayhash-dfcb97ca4fe9277bf9dc1802dd979b071896453b", - build_file = clean_dep("//third_party:highwayhash.BUILD"), + build_file = str(Label("//third_party:highwayhash.BUILD")), ) tf_http_archive( @@ -196,7 +171,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "00b0891c678c065446ca59bcee64719d0096d54d6886e6e472aeee2e170ae324", strip_prefix = "nasm-2.12.02", - build_file = clean_dep("//third_party:nasm.BUILD"), + build_file = str(Label("//third_party:nasm.BUILD")), ) tf_http_archive( @@ -207,7 +182,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "c15a9607892113946379ccea3ca8b85018301b200754f209453ab21674268e77", strip_prefix = "libjpeg-turbo-1.5.1", - build_file = clean_dep("//third_party/jpeg:jpeg.BUILD"), + build_file = str(Label("//third_party/jpeg:jpeg.BUILD")), ) tf_http_archive( @@ -218,7 +193,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "716c59c7dfc808a4c368f8ada526932be72b2fcea11dd85dc9d88b1df1dfe9c2", strip_prefix = "libpng-1.2.53", - build_file = clean_dep("//third_party:png.BUILD"), + build_file = str(Label("//third_party:png.BUILD")), ) tf_http_archive( @@ -229,7 +204,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "208780b3616f9de0aeb50822b7a8f5482f6515193859e91ed61637be6ad74fd4", strip_prefix = "sqlite-amalgamation-3200000", - build_file = clean_dep("//third_party:sqlite.BUILD"), + build_file = str(Label("//third_party:sqlite.BUILD")), ) tf_http_archive( @@ -240,7 +215,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "34a7377ba834397db019e8eb122e551a49c98f49df75ec3fcc92b9a794a4f6d1", strip_prefix = "giflib-5.1.4", - build_file = clean_dep("//third_party:gif.BUILD"), + build_file = str(Label("//third_party:gif.BUILD")), ) tf_http_archive( @@ -251,7 +226,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a", strip_prefix = "six-1.10.0", - build_file = clean_dep("//third_party:six.BUILD"), + build_file = str(Label("//third_party:six.BUILD")), ) tf_http_archive( @@ -262,7 +237,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "ff6d2e2962d834acb125cc4dcc80c54a8c17c253f4cc9d9c43b5102a560bb75d", strip_prefix = "astor-0.6.2", - build_file = clean_dep("//third_party:astor.BUILD"), + build_file = str(Label("//third_party:astor.BUILD")), ) tf_http_archive( @@ -273,7 +248,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "7068908321ecd2774f145193c4b34a11305bd104b4551b09273dfd1d6a374930", strip_prefix = "gast-0.2.0", - build_file = clean_dep("//third_party:gast.BUILD"), + build_file = str(Label("//third_party:gast.BUILD")), ) tf_http_archive( @@ -284,7 +259,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b", strip_prefix = "termcolor-1.1.0", - build_file = clean_dep("//third_party:termcolor.BUILD"), + build_file = str(Label("//third_party:termcolor.BUILD")), ) tf_http_archive( @@ -305,7 +280,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "8813bf712a66b3d8b85dc289e1104ed220f1878cf981e2fe756dfaabe9a82892", strip_prefix = "backports.weakref-1.0rc1/src", - build_file = clean_dep("//third_party:backports_weakref.BUILD"), + build_file = str(Label("//third_party:backports_weakref.BUILD")), ) tf_http_archive( @@ -316,7 +291,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2dadd04a2802de27e0fe5a19b76538f6da9d39ff244036afa00c1bba754de5ee", strip_prefix = "codegen-1.0", - build_file = clean_dep("//third_party:codegen.BUILD"), + build_file = str(Label("//third_party:codegen.BUILD")), ) filegroup_external( @@ -401,7 +376,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://ftp.exim.org/pub/pcre/pcre-8.39.tar.gz", ], strip_prefix = "pcre-8.39", - build_file = clean_dep("//third_party:pcre.BUILD"), + build_file = str(Label("//third_party:pcre.BUILD")), ) tf_http_archive( @@ -413,7 +388,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://pilotfiber.dl.sourceforge.net/project/swig/swig/swig-3.0.8/swig-3.0.8.tar.gz", ], strip_prefix = "swig-3.0.8", - build_file = clean_dep("//third_party:swig.BUILD"), + build_file = str(Label("//third_party:swig.BUILD")), ) tf_http_archive( @@ -424,7 +399,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://curl.haxx.se/download/curl-7.49.1.tar.gz", ], strip_prefix = "curl-7.49.1", - build_file = clean_dep("//third_party:curl.BUILD"), + build_file = str(Label("//third_party:curl.BUILD")), ) tf_http_archive( @@ -446,7 +421,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/antirez/linenoise/archive/c894b9e59f02203dbe4e2be657572cf88c4230c3.tar.gz", ], strip_prefix = "linenoise-c894b9e59f02203dbe4e2be657572cf88c4230c3", - build_file = clean_dep("//third_party:linenoise.BUILD"), + build_file = str(Label("//third_party:linenoise.BUILD")), ) # TODO(phawkins): currently, this rule uses an unofficial LLVM mirror. @@ -459,7 +434,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1efbb9b05af88368be984d2f6526061d4a857181ef10f8841889a3a46869bb01", strip_prefix = "llvm-1c3cdea2f181d8e14ee184466c5fb237f1b4cda8", - build_file = clean_dep("//third_party/llvm:llvm.BUILD"), + build_file = str(Label("//third_party/llvm:llvm.BUILD")), ) tf_http_archive( @@ -470,7 +445,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "108532fb94c6f227558d45be3f3347b52539f0f58290a7bb31ec06c462d05326", strip_prefix = "lmdb-LMDB_0.9.19/libraries/liblmdb", - build_file = clean_dep("//third_party:lmdb.BUILD"), + build_file = str(Label("//third_party:lmdb.BUILD")), ) tf_http_archive( @@ -481,7 +456,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "07d34db40593d257324ec5fb9debc4dc33f29f8fb44e33a2eeb35503e61d0fe2", strip_prefix = "jsoncpp-11086dd6a7eba04289944367ca82cea71299ed70", - build_file = clean_dep("//third_party:jsoncpp.BUILD"), + build_file = str(Label("//third_party:jsoncpp.BUILD")), ) tf_http_archive( @@ -502,7 +477,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "36658cb768a54c1d4dec43c3116c27ed893e88b02ecfcb44f2166f9c0b7f2a0d", strip_prefix = "zlib-1.2.8", - build_file = clean_dep("//third_party:zlib.BUILD"), + build_file = str(Label("//third_party:zlib.BUILD")), ) tf_http_archive( @@ -512,7 +487,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://www.kurims.kyoto-u.ac.jp/~ooura/fft.tgz", ], sha256 = "52bb637c70b971958ec79c9c8752b1df5ff0218a4db4510e60826e0cb79b5296", - build_file = clean_dep("//third_party/fft2d:fft2d.BUILD"), + build_file = str(Label("//third_party/fft2d:fft2d.BUILD")), ) tf_http_archive( @@ -523,7 +498,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2f7504c73d85bac842e893340333be8cb8561710642fc9562fccdd9d2c3fcc94", strip_prefix = "snappy-1.1.4", - build_file = clean_dep("//third_party:snappy.BUILD"), + build_file = str(Label("//third_party:snappy.BUILD")), ) tf_http_archive( @@ -534,7 +509,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ca86fb6179ecbff789cc67c836139c1bbc0324ed8c04643405a30bf26325176", strip_prefix = "nccl-03d856977ecbaac87e598c0c4bafca96761b9ac7", - build_file = clean_dep("//third_party:nccl.BUILD"), + build_file = str(Label("//third_party:nccl.BUILD")), ) tf_http_archive( @@ -545,8 +520,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "dd035d57c8f19b0b612dd6eefe6e5eebad76f506e302cccb7c2066f25a83585e", strip_prefix = "librdkafka-0.11.1", - build_file = clean_dep("//third_party:kafka/BUILD"), - patch_file = clean_dep("//third_party/kafka:config.patch"), + build_file = str(Label("//third_party:kafka/BUILD")), + patch_file = str(Label("//third_party/kafka:config.patch")), ) tf_http_archive( @@ -557,7 +532,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "b888d8ce5fc10254c3dd6c9020c7764dd53cf39cf011249d0b4deda895de1b7c", strip_prefix = "aws-sdk-cpp-1.3.15", - build_file = clean_dep("//third_party:aws.BUILD"), + build_file = str(Label("//third_party:aws.BUILD")), ) java_import_external( @@ -593,7 +568,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "3c8f25c02e806c3ce0ab5fb7da1817f89fc9732709024e2a81b6b82f7cc792a8", strip_prefix = "jemalloc-4.4.0", - build_file = clean_dep("//third_party:jemalloc.BUILD"), + build_file = str(Label("//third_party:jemalloc.BUILD")), ) java_import_external( @@ -638,7 +613,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "e0928ca4aa10ea1e0551e2d7ce4d1d7ea2d84b2abbdef082b0da84268791d0c4", strip_prefix = "pprof-c0fb62ec88c411cc91194465e54db2632845b650", - build_file = clean_dep("//third_party:pprof.BUILD"), + build_file = str(Label("//third_party:pprof.BUILD")), ) tf_http_archive( @@ -649,7 +624,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6bfa06ab52a650ae7ee6963143a0bbc667d6504822cbd9670369b598f18c58c3", strip_prefix = "cub-1.8.0", - build_file = clean_dep("//third_party:cub.BUILD"), + build_file = str(Label("//third_party:cub.BUILD")), ) tf_http_archive( @@ -660,7 +635,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/cython/cython/archive/3732784c45cfb040a5b0936951d196f83a12ea17.tar.gz", ], strip_prefix = "cython-3732784c45cfb040a5b0936951d196f83a12ea17", - build_file = clean_dep("//third_party:cython.BUILD"), + build_file = str(Label("//third_party:cython.BUILD")), delete = ["BUILD.bazel"], ) @@ -682,7 +657,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", "https://github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", ], - build_file = clean_dep("//third_party:arm_neon_2_x86_sse.BUILD"), + build_file = str(Label("//third_party:arm_neon_2_x86_sse.BUILD")), ) tf_http_archive( @@ -693,7 +668,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", "https://github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", ], - build_file = clean_dep("//third_party/flatbuffers:flatbuffers.BUILD"), + build_file = str(Label("//third_party/flatbuffers:flatbuffers.BUILD")), ) tf_http_archive( @@ -703,7 +678,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", ], - build_file = clean_dep("//third_party:tflite_mobilenet.BUILD"), + build_file = str(Label("//third_party:tflite_mobilenet.BUILD")), ) tf_http_archive( @@ -713,7 +688,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip" ], - build_file = clean_dep("//third_party:tflite_smartreply.BUILD"), + build_file = str(Label("//third_party:tflite_smartreply.BUILD")), ) ############################################################################## @@ -777,7 +752,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # Needed by Protobuf native.bind( name = "python_headers", - actual = clean_dep("//util/python:python_headers"), + actual = str(Label("//util/python:python_headers")), ) # Needed by Protobuf diff --git a/third_party/mkl/BUILD b/third_party/mkl/BUILD index c2adf578c7..b27d341404 100644 --- a/third_party/mkl/BUILD +++ b/third_party/mkl/BUILD @@ -1,17 +1,10 @@ licenses(["notice"]) # 3-Clause BSD -config_setting( - name = "using_mkl", - values = { - "define": "using_mkl=true", - }, - visibility = ["//visibility:public"], -) +exports_files(["LICENSE"]) config_setting( - name = "using_mkl_lnx_x64", + name = "using_mkl", values = { - "cpu": "k8", "define": "using_mkl=true", }, visibility = ["//visibility:public"], @@ -22,37 +15,12 @@ load( "if_mkl", ) -filegroup( - name = "LICENSE", - srcs = ["MKL_LICENSE"] + select({ - "@org_tensorflow//tensorflow:linux_x86_64": [ - "@mkl_linux//:LICENSE", - ], - "@org_tensorflow//tensorflow:darwin": [ - "@mkl_darwin//:LICENSE", - ], - "@org_tensorflow//tensorflow:windows": [ - "@mkl_windows//:LICENSE", - ], - }), - visibility = ["//visibility:public"], -) - cc_library( name = "intel_binary_blob", + srcs = if_mkl([ + "@mkl//:libmklml_intel.so", + "@mkl//:libiomp5.so", + ]), visibility = ["//visibility:public"], - deps = select({ - "@org_tensorflow//tensorflow:linux_x86_64": [ - "@mkl_linux//:mkl_headers", - "@mkl_linux//:mkl_libs_linux", - ], - "@org_tensorflow//tensorflow:darwin": [ - "@mkl_darwin//:mkl_headers", - "@mkl_darwin//:mkl_libs_darwin", - ], - "@org_tensorflow//tensorflow:windows": [ - "@mkl_windows//:mkl_headers", - "@mkl_windows//:mkl_libs_windows", - ], - }), + deps = ["@mkl//:mkl_headers"], ) diff --git a/third_party/mkl/MKL_LICENSE b/third_party/mkl/MKL_LICENSE deleted file mode 100644 index 9c8f3ea087..0000000000 --- a/third_party/mkl/MKL_LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/third_party/mkl/build_defs.bzl b/third_party/mkl/build_defs.bzl index 53e02769da..8b73ddabdd 100644 --- a/third_party/mkl/build_defs.bzl +++ b/third_party/mkl/build_defs.bzl @@ -24,18 +24,6 @@ def if_mkl(if_true, if_false = []): "//conditions:default": if_false }) -def if_mkl_lnx_x64(if_true, if_false = []): - """Shorthand for select()'ing on whether we're building with MKL. - - Returns a select statement which evaluates to if_true if we're building - with MKL enabled. Otherwise, the select statement evaluates to if_false. - - """ - return select({ - str(Label("//third_party/mkl:using_mkl_lnx_x64")): if_true, - "//conditions:default": if_false - }) - def _enable_local_mkl(repository_ctx): return _TF_MKL_ROOT in repository_ctx.os.environ diff --git a/third_party/mkl/mkl.BUILD b/third_party/mkl/mkl.BUILD index c3a71e4ff9..8db97232e1 100644 --- a/third_party/mkl/mkl.BUILD +++ b/third_party/mkl/mkl.BUILD @@ -17,29 +17,14 @@ cc_library( visibility = ["//visibility:public"], ) -cc_library( - name = "mkl_libs_linux", - srcs = [ - "lib/libiomp5.so", - "lib/libmklml_intel.so", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "mkl_libs_darwin", - srcs = [ - "lib/libiomp5.dylib", - "lib/libmklml.dylib", - ], +filegroup( + name = "libmklml_intel.so", + srcs = ["lib/libmklml_intel.so"], visibility = ["//visibility:public"], ) -cc_library( - name = "mkl_libs_windows", - srcs = [ - "lib/libiomp5md.lib", - "lib/mklml.lib", - ], +filegroup( + name = "libiomp5.so", + srcs = ["lib/libiomp5.so"], visibility = ["//visibility:public"], ) -- GitLab From a0e0685ca974e484de9200caf8c414dcb55277bb Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 17:06:44 -0700 Subject: [PATCH 382/906] Collective Ops Part 1 The basic interface definitions, local-only versions of remote-access, param-resolution, device-resolution and mgr. A collective op is able to execute synchronously across devices and across separate graphs. Collective ops to be introduced eventually include broadcast and all-reduce. This change is part of a series of changes that will introduce the necessary infrastructure then the initial op implementations. PiperOrigin-RevId: 190860248 --- tensorflow/core/BUILD | 16 + .../core/common_runtime/buf_rendezvous.cc | 166 +++++ .../core/common_runtime/buf_rendezvous.h | 103 +++ .../common_runtime/buf_rendezvous_test.cc | 197 ++++++ .../common_runtime/collective_executor_mgr.cc | 114 +++ .../common_runtime/collective_executor_mgr.h | 70 ++ .../collective_executor_mgr_test.cc | 98 +++ .../collective_param_resolver_local.cc | 666 ++++++++++++++++++ .../collective_param_resolver_local.h | 209 ++++++ .../collective_param_resolver_local_test.cc | 151 ++++ .../common_runtime/collective_rma_local.cc | 108 +++ .../common_runtime/collective_rma_local.h | 88 +++ .../collective_rma_local_test.cc | 148 ++++ .../common_runtime/device_resolver_local.cc | 49 ++ .../common_runtime/device_resolver_local.h | 48 ++ .../device_resolver_local_test.cc | 87 +++ tensorflow/core/framework/collective.cc | 120 ++++ tensorflow/core/framework/collective.h | 308 ++++++++ tensorflow/core/framework/op_kernel.h | 1 + 19 files changed, 2747 insertions(+) create mode 100644 tensorflow/core/common_runtime/buf_rendezvous.cc create mode 100644 tensorflow/core/common_runtime/buf_rendezvous.h create mode 100644 tensorflow/core/common_runtime/buf_rendezvous_test.cc create mode 100644 tensorflow/core/common_runtime/collective_executor_mgr.cc create mode 100644 tensorflow/core/common_runtime/collective_executor_mgr.h create mode 100644 tensorflow/core/common_runtime/collective_executor_mgr_test.cc create mode 100644 tensorflow/core/common_runtime/collective_param_resolver_local.cc create mode 100644 tensorflow/core/common_runtime/collective_param_resolver_local.h create mode 100644 tensorflow/core/common_runtime/collective_param_resolver_local_test.cc create mode 100644 tensorflow/core/common_runtime/collective_rma_local.cc create mode 100644 tensorflow/core/common_runtime/collective_rma_local.h create mode 100644 tensorflow/core/common_runtime/collective_rma_local_test.cc create mode 100644 tensorflow/core/common_runtime/device_resolver_local.cc create mode 100644 tensorflow/core/common_runtime/device_resolver_local.h create mode 100644 tensorflow/core/common_runtime/device_resolver_local_test.cc create mode 100644 tensorflow/core/framework/collective.cc create mode 100644 tensorflow/core/framework/collective.h diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 4726946277..712106492b 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -455,6 +455,7 @@ tf_cuda_library( "framework/attr_value_util.h", "framework/bfloat16.h", "framework/cancellation.h", + "framework/collective.h", "framework/common_shape_fns.h", "framework/control_flow.h", # TODO(josh11b): Make internal? "framework/dataset.h", @@ -2172,6 +2173,11 @@ tf_cuda_library( CORE_CPU_LIB_HEADERS = CORE_CPU_BASE_HDRS + [ "common_runtime/allocator_retry.h", "common_runtime/bfc_allocator.h", + "common_runtime/collective_executor_mgr.h", + "common_runtime/collective_param_resolver_local.h", + "common_runtime/collective_rma_local.h", + "common_runtime/device_resolver_local.h", + "common_runtime/buf_rendezvous.h", "common_runtime/build_graph_options.h", "common_runtime/constant_folding.h", "common_runtime/copy_tensor.h", @@ -2210,7 +2216,11 @@ tf_cuda_library( "common_runtime/accumulate_n_optimizer.cc", "common_runtime/allocator_retry.cc", "common_runtime/bfc_allocator.cc", + "common_runtime/buf_rendezvous.cc", "common_runtime/build_graph_options.cc", + "common_runtime/collective_executor_mgr.cc", + "common_runtime/collective_param_resolver_local.cc", + "common_runtime/collective_rma_local.cc", "common_runtime/constant_folding.cc", "common_runtime/copy_tensor.cc", "common_runtime/costmodel_manager.cc", @@ -2218,6 +2228,7 @@ tf_cuda_library( "common_runtime/device.cc", "common_runtime/device_factory.cc", "common_runtime/device_mgr.cc", + "common_runtime/device_resolver_local.cc", "common_runtime/device_set.cc", "common_runtime/executor.cc", "common_runtime/function.cc", @@ -2825,6 +2836,11 @@ tf_cc_tests( name = "higher_level_tests", size = "small", srcs = [ + "common_runtime/buf_rendezvous_test.cc", + "common_runtime/collective_executor_mgr_test.cc", + "common_runtime/collective_param_resolver_local_test.cc", + "common_runtime/collective_rma_local_test.cc", + "common_runtime/device_resolver_local_test.cc", "common_runtime/device_set_test.cc", "common_runtime/optimization_registry_test.cc", "common_runtime/pending_counts_test.cc", diff --git a/tensorflow/core/common_runtime/buf_rendezvous.cc b/tensorflow/core/common_runtime/buf_rendezvous.cc new file mode 100644 index 0000000000..b57eb2943a --- /dev/null +++ b/tensorflow/core/common_runtime/buf_rendezvous.cc @@ -0,0 +1,166 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/buf_rendezvous.h" + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/process_util.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/core/notification.h" + +namespace tensorflow { + +BufRendezvous::~BufRendezvous() { + mutex_lock l(mu_); + if (!hook_table_.empty()) { + PurgeTable(errors::Internal("Delete called on non-empty BufRendezvous"), + &hook_table_); + } +} + +void BufRendezvous::StartAbort(const Status& s) { + CHECK(!s.ok()); + HookTable dummy_table; + { + mutex_lock l(mu_); + status_.Update(s); + hook_table_.swap(dummy_table); + } + PurgeTable(s, &dummy_table); +} + +void BufRendezvous::PurgeTable(const Status& s, HookTable* table) { + for (auto& it : *table) { + Hook* h = it.second; + if (h->cons_cb != nullptr) { + h->cons_cb(s, nullptr); + } + if (h->prod_cb != nullptr) { + h->prod_cb(s); + } + delete h; + } + table->clear(); +} + +string BufRendezvous::Hook::DebugString() const { + return strings::StrCat("[dev:", (prod_dev ? prod_dev->name() : "none"), + ", ctx:", reinterpret_cast(prod_ctx), + ", val:", reinterpret_cast(prod_value), + ", pcb:", reinterpret_cast(&prod_cb), + ", ccb:", reinterpret_cast(&cons_cb), "]"); +} + +void BufRendezvous::ProvideBuf(const string& key, Device* dev, + DeviceContext* dev_ctx, const Tensor* v, + const AllocatorAttributes& attr, + const ProducerCallback& done) { + Hook* h = nullptr; + Status providebuf_status; + do { + mutex_lock l(mu_); + if (!status_.ok()) { + providebuf_status = status_; + break; + } else { + auto it = hook_table_.find(key); + if (it == hook_table_.end()) { + h = new Hook; + it = hook_table_.insert(std::make_pair(key, h)).first; + } else { + if (it->second->prod_cb != nullptr) { + providebuf_status = errors::Internal( + "BufRendezvous::ProvideBuf already called for key ", key); + break; + } + h = it->second; + } + // Populate Hook with all of the prod values. + h->prod_dev = dev; + h->prod_ctx = dev_ctx; + h->prod_value = v; + h->prod_attr = attr; + h->prod_cb = done; + // If consumer is waiting, kick off right away, removing Hook from table. + if (h->cons_cb != nullptr) { + hook_table_.erase(it); + } else { + h = nullptr; + } + } + } while (false); + if (h) { + h->cons_cb(Status::OK(), h); + } + if (!providebuf_status.ok()) { + done(providebuf_status); + } +} + +void BufRendezvous::ConsumeBuf(const string& key, + const ConsumerCallback& done) { + Hook* existing_hook = nullptr; + Status consumebuf_status; + do { + mutex_lock l(mu_); + if (!status_.ok()) { + consumebuf_status = status_; + break; + } + auto it = hook_table_.find(key); + if (it != hook_table_.end()) { + // Prepare to consume immediately. + if (it->second->cons_cb) { + consumebuf_status = + errors::Internal("Second consumer arrived for key ", key); + break; + } + existing_hook = it->second; + hook_table_.erase(it); + existing_hook->cons_cb = done; + } else { + // Hang consumer callback on the Hook. + Hook* h = new Hook; + hook_table_[key] = h; + h->cons_cb = done; + return; + } + } while (false); + if (existing_hook) { + existing_hook->cons_cb(Status::OK(), existing_hook); + return; + } + if (!consumebuf_status.ok()) { + done(consumebuf_status, nullptr); + return; + } +} + +/*static*/ +void BufRendezvous::DoneWithHook(Hook* h) { + h->prod_cb(Status::OK()); + delete h; +} + +void BufRendezvous::LogContents() { + mutex_lock l(mu_); + LOG(INFO) << strings::StrCat("BufRendezvous ", + strings::Hex(reinterpret_cast(this)), + " step_id=", step_id_, " current contents:"); + for (auto it : hook_table_) { + LOG(INFO) << it.first << ":" << it.second->DebugString(); + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/buf_rendezvous.h b/tensorflow/core/common_runtime/buf_rendezvous.h new file mode 100644 index 0000000000..e94e88b323 --- /dev/null +++ b/tensorflow/core/common_runtime/buf_rendezvous.h @@ -0,0 +1,103 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMMON_RUNTIME_BUF_RENDEZVOUS_H_ +#define TENSORFLOW_COMMON_RUNTIME_BUF_RENDEZVOUS_H_ + +#include +#include + +#include "tensorflow/core/framework/allocator.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/gtl/flatmap.h" +#include "tensorflow/core/platform/mutex.h" + +namespace tensorflow { +class Device; +class DeviceContext; +class Tensor; + +// EXPERIMENTAL: RDMA oriented producer/consumer rendezvous on a local +// Tensor value for which DMAHelper::CanUseDMA() is true, i.e. dense +// numeric types. Similar to Rendezvous but never owns a Ref on the +// tensor, instead it uses an explicit callback to the producer when +// the consumer side is finished with the value. This allows the +// producer to perform in-place updates on the source buffer or to take +// other actions that depend on knowing the consumer has passed a certain +// execution point. +class BufRendezvous { + public: + explicit BufRendezvous(uint64 step_id) : step_id_(step_id) {} + + ~BufRendezvous(); + + // Inform all all waiting parties that this BufRendezvous is defunct + // because of an error Status interrupting the Step. + void StartAbort(const Status& s); + + struct Hook; + // Provided by the consumer to be called when access to the buffer + // is available. If the Status arg is not OK, then hook will not + // be populated. Ownership of Hook passes to consumer with the + // callback. + typedef std::function ConsumerCallback; + // Provided by the producer to be called when the consumer has finished + // reading the buffer and will no longer access it. + typedef std::function ProducerCallback; + + struct Hook { + Device* prod_dev; + DeviceContext* prod_ctx; + const Tensor* prod_value; + AllocatorAttributes prod_attr; + ProducerCallback prod_cb; + ConsumerCallback cons_cb; + Hook() + : prod_dev(nullptr), + prod_ctx(nullptr), + prod_value(nullptr), + prod_cb(nullptr), + cons_cb(nullptr) {} + string DebugString() const; + }; + + // Called to advertise availability of a Tensor value corresponding + // to key. That value must stay valid until done is called. + void ProvideBuf(const string& key, Device* dev, DeviceContext* dev_ctx, + const Tensor* v, const AllocatorAttributes& attr, + const ProducerCallback& done); + + // Called to request access to a Tensor value corresponding to key. + // Consumer is provide with a Hook as soon as availble. + void ConsumeBuf(const string& key, const ConsumerCallback& done); + + // Consumer must call this function when it's done reading the Hook provided + // by the ConsumerCallback. This function will invoke the producer callback + // and then delete h. + static void DoneWithHook(Hook* h); + + // Write the current contents of the table to the INFO log. + void LogContents(); + + protected: + const uint64 step_id_; + mutex mu_; + Status status_ GUARDED_BY(mu_); + typedef gtl::FlatMap HookTable; + HookTable hook_table_ GUARDED_BY(mu_); + + void PurgeTable(const Status& s, HookTable* table); +}; +} // namespace tensorflow +#endif // TENSORFLOW_COMMON_RUNTIME_BUF_RENDEZVOUS_H_ diff --git a/tensorflow/core/common_runtime/buf_rendezvous_test.cc b/tensorflow/core/common_runtime/buf_rendezvous_test.cc new file mode 100644 index 0000000000..0e798235bf --- /dev/null +++ b/tensorflow/core/common_runtime/buf_rendezvous_test.cc @@ -0,0 +1,197 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/buf_rendezvous.h" + +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/types.pb.h" +#include "tensorflow/core/lib/core/notification.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace { + +#define NUM_DEVS 3 + +class BufRendezvousTest : public ::testing::Test { + protected: + BufRendezvousTest() { + br_.reset(new BufRendezvous(123)); + fake_dev_ptr_ = reinterpret_cast(512LLU); + fake_dev_ctx_ = reinterpret_cast(1024LLU); + a_ = Tensor(DT_FLOAT, TensorShape({24})); + b_ = Tensor(DT_FLOAT, TensorShape({24})); + } + + Device* fake_dev_ptr_ = nullptr; + DeviceContext* fake_dev_ctx_ = nullptr; + Tensor a_; + Tensor b_; + AllocatorAttributes aa_; + std::unique_ptr br_; +}; + +TEST_F(BufRendezvousTest, CorrectUseProducerFirst) { + Status prod_status; + Status cons_status; + bool prod_callback_called = false; + bool cons_callback_called = false; + Notification note; + br_->ProvideBuf( + "key0", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [¬e, &prod_status, &prod_callback_called](const Status& s) { + prod_status = s; + prod_callback_called = true; + note.Notify(); + }); + EXPECT_FALSE(prod_callback_called); + br_->ConsumeBuf("key0", [this, &cons_status, &cons_callback_called]( + const Status& s, BufRendezvous::Hook* h) { + cons_status = s; + cons_callback_called = true; + ASSERT_TRUE(h != nullptr); + EXPECT_EQ(h->prod_dev, fake_dev_ptr_); + EXPECT_EQ(h->prod_ctx, fake_dev_ctx_); + EXPECT_EQ(h->prod_value, &a_); + br_->DoneWithHook(h); + }); + EXPECT_TRUE(cons_callback_called); + note.WaitForNotification(); + EXPECT_TRUE(prod_callback_called); + TF_EXPECT_OK(cons_status); + TF_EXPECT_OK(prod_status); +} + +TEST_F(BufRendezvousTest, CorrectUseConsumerFirst) { + Status prod_status; + Status cons_status; + bool prod_callback_called = false; + bool cons_callback_called = false; + Notification note; + br_->ConsumeBuf("key0", [this, &cons_status, &cons_callback_called]( + const Status& s, BufRendezvous::Hook* h) { + cons_status = s; + cons_callback_called = true; + ASSERT_TRUE(h != nullptr); + EXPECT_EQ(h->prod_dev, fake_dev_ptr_); + EXPECT_EQ(h->prod_ctx, fake_dev_ctx_); + EXPECT_EQ(h->prod_value, &a_); + br_->DoneWithHook(h); + }); + EXPECT_FALSE(cons_callback_called); + br_->ProvideBuf( + "key0", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [¬e, &prod_status, &prod_callback_called](const Status& s) { + prod_status = s; + prod_callback_called = true; + note.Notify(); + }); + EXPECT_TRUE(cons_callback_called); + note.WaitForNotification(); + EXPECT_TRUE(prod_callback_called); + TF_EXPECT_OK(cons_status); + TF_EXPECT_OK(prod_status); +} + +TEST_F(BufRendezvousTest, ErrorDuplicatePut) { + bool prod_callback_called = false; + br_->ProvideBuf("key0", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [this, &prod_callback_called](const Status& s) { + prod_callback_called = true; + }); + Status bad_status; + Notification note; + br_->ProvideBuf("key0", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [&bad_status, ¬e](const Status& s) { + bad_status = s; + note.Notify(); + }); + note.WaitForNotification(); + EXPECT_FALSE(bad_status.ok()); + EXPECT_EQ("BufRendezvous::ProvideBuf already called for key key0", + bad_status.error_message()); + EXPECT_FALSE(prod_callback_called); + br_.reset(); +} + +TEST_F(BufRendezvousTest, ErrorDeleteNonEmpty) { + Status cons_status; + br_->ConsumeBuf( + "key0", [this, &cons_status](const Status& s, BufRendezvous::Hook* h) { + cons_status = s; + EXPECT_EQ(h, nullptr); + }); + EXPECT_TRUE(cons_status.ok()); + br_.reset(); + EXPECT_FALSE(cons_status.ok()); + EXPECT_EQ("Delete called on non-empty BufRendezvous", + cons_status.error_message()); +} + +TEST_F(BufRendezvousTest, AbortNonEmpty) { + Status cons_status; + Status prod_status; + Notification prod_note; + Notification cons_note; + br_->ConsumeBuf("key0", [this, &cons_note, &cons_status]( + const Status& s, BufRendezvous::Hook* h) { + cons_status = s; + cons_note.Notify(); + }); + br_->ProvideBuf("key1", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [this, &prod_note, &prod_status](const Status& s) { + prod_status = s; + prod_note.Notify(); + }); + br_->StartAbort(errors::Internal("Falling sky detected")); + prod_note.WaitForNotification(); + cons_note.WaitForNotification(); + EXPECT_FALSE(prod_status.ok()); + EXPECT_EQ(prod_status.error_message(), "Falling sky detected"); + EXPECT_FALSE(cons_status.ok()); + EXPECT_EQ(cons_status.error_message(), "Falling sky detected"); +} + +TEST_F(BufRendezvousTest, AbortEmpty) { + br_->StartAbort(errors::Internal("Falling sky detected")); +} + +TEST_F(BufRendezvousTest, UseAfterAbort) { + br_->StartAbort(errors::Internal("Falling sky detected")); + Status cons_status; + Status prod_status; + Notification prod_note; + Notification cons_note; + br_->ConsumeBuf("key0", [this, &cons_note, &cons_status]( + const Status& s, BufRendezvous::Hook* h) { + cons_status = s; + cons_note.Notify(); + }); + br_->ProvideBuf("key1", fake_dev_ptr_, fake_dev_ctx_, &a_, aa_, + [this, &prod_note, &prod_status](const Status& s) { + prod_status = s; + prod_note.Notify(); + }); + prod_note.WaitForNotification(); + cons_note.WaitForNotification(); + EXPECT_FALSE(prod_status.ok()); + EXPECT_EQ(prod_status.error_message(), "Falling sky detected"); + EXPECT_FALSE(cons_status.ok()); + EXPECT_EQ(cons_status.error_message(), "Falling sky detected"); +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_executor_mgr.cc b/tensorflow/core/common_runtime/collective_executor_mgr.cc new file mode 100644 index 0000000000..a5c4946e58 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_executor_mgr.cc @@ -0,0 +1,114 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_executor_mgr.h" + +#include "tensorflow/core/common_runtime/build_graph_options.h" +#include "tensorflow/core/common_runtime/collective_rma_local.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/framework/collective.h" +#include "tensorflow/core/protobuf/config.pb.h" + +namespace tensorflow { +namespace { +// TODO(tucker): Temporary class just until a real CollectiveExecutor +// implementation is submitted in a later CL. +class DummyCollectiveExecutor : public CollectiveExecutor { + public: + explicit DummyCollectiveExecutor(CollectiveExecutorMgr* ce_mgr) + : CollectiveExecutor(ce_mgr) {} + + ~DummyCollectiveExecutor() override {} + + void RecvFromPeer(const string& peer_device, const string& peer_task, + bool peer_is_local, const string& key, Device* to_device, + DeviceContext* to_device_ctx, + const AllocatorAttributes& to_alloc_attr, Tensor* to_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) override { + done(errors::Internal("Unimplemented")); + } + + void PostToPeer(const string& peer_device, const string& peer_task, + const string& key, Device* from_device, + DeviceContext* from_device_ctx, + const AllocatorAttributes& from_alloc_attr, + const Tensor* from_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) override { + done(errors::Internal("Unimplemented")); + } + + private: + TF_DISALLOW_COPY_AND_ASSIGN(DummyCollectiveExecutor); +}; +} // namespace + +CollectiveExecutorMgr::CollectiveExecutorMgr( + const ConfigProto& config, const DeviceMgr* dev_mgr, + DeviceResolverInterface* dev_resolver, + ParamResolverInterface* param_resolver) + : dev_mgr_(dev_mgr), + dev_resolver_(dev_resolver), + param_resolver_(param_resolver) {} + +CollectiveExecutorMgr::~CollectiveExecutorMgr() { + for (auto iter : executor_table_) { + iter.second->Unref(); + } +} + +CollectiveExecutor* CollectiveExecutorMgr::FindOrCreate(int64 step_id) { + CollectiveExecutor* ce = nullptr; + { + mutex_lock l(exec_mu_); + auto it = executor_table_.find(step_id); + if (it != executor_table_.end()) { + ce = it->second; + } else { + ce = new DummyCollectiveExecutor(this); + executor_table_[step_id] = ce; + } + ce->Ref(); + } + return ce; +} + +void CollectiveExecutorMgr::Cleanup(int64 step_id) { + CollectiveExecutor* ce = nullptr; + { + mutex_lock l(exec_mu_); + auto it = executor_table_.find(step_id); + if (it != executor_table_.end()) { + ce = it->second; + executor_table_.erase(it); + } + } + if (ce) ce->Unref(); +} + +void CollectiveExecutorMgr::GetStepSequenceAsync( + const GetStepSequenceRequest* request, GetStepSequenceResponse* response, + const StatusCallback& done) { + done(errors::Internal( + "CollectiveExecutorMgr does not implement GetStepSequence.")); +} + +void CollectiveExecutorMgr::RefreshStepIdSequenceAsync( + int64 graph_key, const StatusCallback& done) { + done(errors::Internal( + "CollectiveExecutorMgr does not implement RefreshStepIdSequence.")); +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_executor_mgr.h b/tensorflow/core/common_runtime/collective_executor_mgr.h new file mode 100644 index 0000000000..4b42e2b4d1 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_executor_mgr.h @@ -0,0 +1,70 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_EXECUTOR_MGR_H_ +#define TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_EXECUTOR_MGR_H_ + +#include "tensorflow/core/framework/collective.h" +#include "tensorflow/core/lib/gtl/flatmap.h" + +namespace tensorflow { +class ConfigProto; +class DeviceMgr; + +class CollectiveExecutorMgr : public CollectiveExecutorMgrInterface { + public: + CollectiveExecutorMgr(const ConfigProto& config, const DeviceMgr* dev_mgr, + DeviceResolverInterface* dev_resolver, + ParamResolverInterface* param_resolver); + + virtual ~CollectiveExecutorMgr(); + + CollectiveExecutor* FindOrCreate(int64 step_id) override; + + void Cleanup(int64 step_id) override; + + ParamResolverInterface* GetParamResolver() const override { + return param_resolver_.get(); + } + + DeviceResolverInterface* GetDeviceResolver() const override { + return dev_resolver_.get(); + } + + void GetStepSequenceAsync(const GetStepSequenceRequest* request, + GetStepSequenceResponse* response, + const StatusCallback& done) override; + + void RefreshStepIdSequenceAsync(int64 graph_key, + const StatusCallback& done) override; + + int64 NextStepId(int64 graph_key) override { + return CollectiveExecutor::kInvalidId; + } + + void RetireStepId(int64 graph_key, int64 step_id) override {} + + protected: + const DeviceMgr* dev_mgr_; + std::unique_ptr dev_resolver_; + std::unique_ptr param_resolver_; + CollectiveRemoteAccess* remote_access_; + string task_name_; + mutex exec_mu_; + // Map from step_id to CollectiveExecutor + gtl::FlatMap executor_table_ GUARDED_BY(exec_mu_); +}; + +} // namespace tensorflow +#endif // TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_EXECUTOR_MGR_H_ diff --git a/tensorflow/core/common_runtime/collective_executor_mgr_test.cc b/tensorflow/core/common_runtime/collective_executor_mgr_test.cc new file mode 100644 index 0000000000..34c9163d6a --- /dev/null +++ b/tensorflow/core/common_runtime/collective_executor_mgr_test.cc @@ -0,0 +1,98 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_executor_mgr.h" + +#include "tensorflow/core/common_runtime/collective_param_resolver_local.h" +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/common_runtime/device_resolver_local.h" +#include "tensorflow/core/lib/core/notification.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" + +namespace tensorflow { +namespace { + +#define NUM_DEVS 3 + +class CollectiveExecutorMgrTest : public ::testing::Test { + protected: + CollectiveExecutorMgrTest() { + ConfigProto cp; + SessionOptions options; + auto* device_count = options.config.mutable_device_count(); + string task_name = "/job:localhost/replica:0/task:0"; + device_count->insert({"CPU", NUM_DEVS}); + TF_CHECK_OK(DeviceFactory::AddDevices(options, task_name, &devices_)); + device_mgr_.reset(new DeviceMgr(devices_)); + DeviceResolverLocal* drl = new DeviceResolverLocal(device_mgr_.get()); + cme_.reset(new CollectiveExecutorMgr( + cp, device_mgr_.get(), drl, + new CollectiveParamResolverLocal(device_mgr_.get(), drl, task_name))); + } + + std::unique_ptr cme_; + std::vector devices_; + std::unique_ptr device_mgr_; +}; + +TEST_F(CollectiveExecutorMgrTest, FindOrCreate) { + CollectiveExecutor::Handle* h = + new CollectiveExecutor::Handle(cme_->FindOrCreate(1), true); + EXPECT_TRUE(h->get()); + CollectiveExecutor::Handle* h2 = + new CollectiveExecutor::Handle(cme_->FindOrCreate(1), true); + EXPECT_EQ(h->get(), h2->get()); + CollectiveExecutor* ce = h->get(); + delete h; + delete h2; + CollectiveExecutor::Handle h3(cme_->FindOrCreate(1), true); + EXPECT_EQ(ce, h3.get()); + cme_->Cleanup(1); +} + +TEST_F(CollectiveExecutorMgrTest, StepSequenceRelated) { + EXPECT_EQ(CollectiveExecutor::kInvalidId, cme_->NextStepId(123)); + Notification ss_note; + Status ss_status; + cme_->RefreshStepIdSequenceAsync( + 123, [this, &ss_status, &ss_note](const Status& s) { + ss_status = s; + ss_note.Notify(); + }); + ss_note.WaitForNotification(); + EXPECT_FALSE(ss_status.ok()); + EXPECT_EQ(ss_status.error_message(), + "CollectiveExecutorMgr does not implement RefreshStepIdSequence."); + Notification gs_note; + Status gs_status; + GetStepSequenceRequest* req = nullptr; + GetStepSequenceResponse* resp = nullptr; + cme_->GetStepSequenceAsync(req, resp, + [this, &gs_status, &gs_note](const Status& s) { + gs_status = s; + gs_note.Notify(); + }); + gs_note.WaitForNotification(); + EXPECT_FALSE(gs_status.ok()); + EXPECT_EQ(gs_status.error_message(), + "CollectiveExecutorMgr does not implement GetStepSequence."); +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.cc b/tensorflow/core/common_runtime/collective_param_resolver_local.cc new file mode 100644 index 0000000000..b34950b2f4 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_param_resolver_local.cc @@ -0,0 +1,666 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_param_resolver_local.h" + +#include "tensorflow/core/common_runtime/device_mgr.h" + +namespace tensorflow { + +CollectiveParamResolverLocal::CollectiveParamResolverLocal( + const DeviceMgr* dev_mgr, DeviceResolverInterface* dev_resolver, + const string& task_name) + : dev_mgr_(dev_mgr), dev_resolver_(dev_resolver), task_name_(task_name) {} + +void CollectiveParamResolverLocal::CompleteGroupAsync( + const CompleteGroupRequest* request, CompleteGroupResponse* response, + CancellationManager* cancel_mgr, const StatusCallback& done) { + done( + errors::Internal("CompleteGroup is not implemented by " + "CollectiveParamResolverLocal which is " + "intended only for non-distributed deployment.")); +} + +void CollectiveParamResolverLocal::CompleteGroupLocal( + const string& device, CollectiveParams* cp, const GroupRecCallback& done) { + VLOG(1) << "CompleteGroupLocal " << cp << ": " << cp->ToString(); + std::vector to_be_called; + GroupRec* gr = nullptr; + { + mutex_lock l(group_mu_); + auto it = group_table_.find(cp->group.group_key); + if (it == group_table_.end()) { + gr = new GroupRec; + gr->group.group_key = cp->group.group_key; + gr->group.group_size = cp->group.group_size; + gr->group.device_type = cp->group.device_type; + group_table_[gr->group.group_key].reset(gr); + VLOG(2) << "New group_key=" << gr->group.group_key + << " group_size=" << gr->group.group_size; + } else { + gr = it->second.get(); + } + } + Status status; + { + mutex_lock gr_lock(gr->mu); + if (!gr->device_set.empty()) { + // Check for consistency with existing GroupRec. + if (cp->group.device_type != gr->group.device_type) { + status = errors::Internal( + "Collective Op ", cp->name, " is assigned to device ", device, + " with type ", cp->group.device_type.type_string(), + " and group_key ", cp->group.group_key, " but that group has type ", + gr->group.device_type.type_string()); + } else if (cp->group.group_size != gr->group.group_size) { + status = errors::Internal( + "Collective Op ", cp->name, " has group_size ", + cp->group.group_size, " and group_key", cp->group.group_key, + " but that group has size ", gr->group.group_size); + } + } + if (status.ok()) { + // Insert device if not already present. + auto it = gr->device_set.find(device); + if (it == gr->device_set.end()) { + if (gr->device_set.size() == gr->group.group_size) { + // The group is already full. + status = errors::Internal( + "Collective Op ", cp->name, " is assigned to device ", device, + " and group_key ", cp->group.group_key, + " but that group doesn't contain that device."); + } else { + // This is a new device that has not yet joined the group. + gr->device_set.insert(device); + gr->device_list.push_back(device); + DeviceNameUtils::ParsedName parsed_device; + DeviceNameUtils::ParseFullName(device, &parsed_device); + string task_name = strings::StrCat("/job:", parsed_device.job, + "/replica:", parsed_device.replica, + "/task:", parsed_device.task); + gr->task_set.insert(task_name); + gr->task_list.push_back(task_name); + gr->group.num_tasks = static_cast(gr->task_set.size()); + VLOG(1) << "group_key=" << gr->group.group_key + << " group_size=" << gr->group.group_size + << " dev_set=" << gr->device_set.size(); + } + } + } + + if (status.ok()) { + // If the group is not yet complete, queue to wait for it. + VLOG(2) << "group_size " << gr->group.group_size << " set size " + << gr->device_set.size() << " gr " << gr; + + if (gr->device_set.size() < gr->group.group_size) { + gr->waiting.push_back(std::bind(done, std::placeholders::_1, gr)); + return; + } + CHECK_EQ(gr->device_set.size(), gr->group.group_size); + if (!gr->waiting.empty()) { + std::swap(to_be_called, gr->waiting); + } + } + } + done(status, gr); + for (int i = 0; i < to_be_called.size(); ++i) { + to_be_called[i](Status::OK()); + } +} + +namespace { + +struct DevRec { + string task; + string device; + int original_rank; + int local_rank; + int global_rank; + const DeviceLocality* locality; +}; +typedef std::unordered_map TaskDeviceMap; +typedef std::unordered_map GlobalDeviceMap; + +// Create a populated GlobalDeviceMap from CollInstanceParams and localities. +GlobalDeviceMap BuildDevRecs(const CollInstanceParams& ip, + const std::vector& localities) { + GlobalDeviceMap gdm; + CHECK_EQ(ip.device_names.size(), ip.task_names.size()); + CHECK_EQ(ip.device_names.size(), localities.size()); + for (int i = 0; i < ip.device_names.size(); ++i) { + TaskDeviceMap& tdm = gdm[ip.task_names[i]]; + DevRec* dr = &tdm[ip.device_names[i]]; + dr->task = ip.task_names[i]; + dr->device = ip.device_names[i]; + dr->original_rank = i; + dr->local_rank = 0; // Will be populated later by OrderTaskDeviceMap. + dr->global_rank = 0; // Will be populated later by EstablishGlobalRank. + dr->locality = &localities[i]; + } + return gdm; +} + +void OrderTaskDeviceMap(TaskDeviceMap* tdm) { + CHECK_GT(tdm->size(), 0); // Should never be called with 0 devices + int least_rank = -1; + string next_device; + std::set selected; + // Starting device is one with the least initial rank. + for (const auto& it : *tdm) { + if (least_rank < 0 || it.second.original_rank < least_rank) { + least_rank = it.second.original_rank; + next_device = it.second.device; + } + } + CHECK_GE(least_rank, 0); + DeviceNameUtils::ParsedName parsed_name; + CHECK(DeviceNameUtils::ParseFullName(next_device, &parsed_name)); + // NOTE: InterconnectLink has only a device_id, nothing more, so for + // the time being if there's more than one device at a task we + // assume they're all GPUs. + + int next_rank = 0; + while (true) { + selected.insert(next_device); + DevRec* dr = &(*tdm)[next_device]; + dr->local_rank = next_rank; + ++next_rank; + if (selected.size() == tdm->size()) { + break; + } + // For the present time we assume Locality links only cover GPUs. + // For multiple CPUs, just take them in order. + const InterconnectLink* best_link = nullptr; + if (parsed_name.type == "GPU") { + for (const InterconnectLink& il : dr->locality->links().link()) { + parsed_name.id = il.device_id(); + string endpoint_device = + DeviceNameUtils::ParsedNameToString(parsed_name); + if (selected.find(endpoint_device) != selected.end()) { + continue; + } + if (best_link == nullptr || il.strength() > best_link->strength()) { + best_link = &il; + } + } + } + if (best_link != nullptr) { + // Follow the best edge + parsed_name.id = best_link->device_id(); + next_device = DeviceNameUtils::ParsedNameToString(parsed_name); + } else { + // No good edges, alas. Pick the lowest initial rank among remaining + // devices. + least_rank = -1; + for (const auto& it : *tdm) { + if (selected.find(it.second.device) != selected.end()) { + continue; + } + if (least_rank < 0 || it.second.original_rank < least_rank) { + least_rank = it.second.original_rank; + next_device = it.second.device; + } + } + CHECK_GE(least_rank, 0); + } + } +} + +// The first time a shared CollectiveParams is established for a +// shared set of instances we compute a good rank order for all the +// devices in the group, that is appropriate for a ring algorithm. +// This order need not be the same across different instance groups +// sharing the same device group where there is more than one good +// order. +GlobalDeviceMap EstablishGlobalRank( + CollectiveParams* cp, const std::vector& localities) { + VLOG(1) << "EstablishGlobalRank"; + GlobalDeviceMap gdm = BuildDevRecs(cp->instance, localities); + for (auto& iter : gdm) { + TaskDeviceMap& tdm = iter.second; + OrderTaskDeviceMap(&tdm); + } + // Connect the global rank order by the order in which tasks first appear. + std::set ordered_tasks; + int next_rank = 0; + for (int i = 0; i < cp->instance.task_names.size(); ++i) { + const string& task_name = cp->instance.task_names[i]; + if (ordered_tasks.find(task_name) != ordered_tasks.end()) { + continue; + } + ordered_tasks.insert(task_name); + TaskDeviceMap* tdm = &gdm[task_name]; + for (auto& it : *tdm) { + it.second.global_rank = it.second.local_rank + next_rank; + } + next_rank += tdm->size(); + } + return gdm; +} + +// Sort cp->instance.device_names lexicographically, but do by first +// computing a reordering permutation so we can keep cp->instance.task_names +// in corresponding order. +void SortDevicesAndTasks(CollectiveParams* cp) { + VLOG(1) << "SortDevicesAndTasks " << cp << " instance " << &cp->instance; + CHECK(cp); + CHECK_EQ(cp->group.group_size, cp->instance.device_names.size()); + CHECK_EQ(cp->group.group_size, cp->instance.task_names.size()); + std::vector perm(cp->group.group_size); + // TODO(tucker): substitute std::iota when the windows build supports it. + // std::iota(perm.begin(), perm.end(), 0); + for (int i = 0; i < perm.size(); ++i) { + perm[i] = i; + } + std::sort(perm.begin(), perm.end(), [cp](const int& a, const int& b) { + return cp->instance.device_names[a] < cp->instance.device_names[b]; + }); + std::vector new_devs; + std::vector new_tasks; + new_devs.reserve(cp->group.group_size); + new_tasks.reserve(cp->group.group_size); + for (int pi : perm) { + new_devs.push_back(cp->instance.device_names[pi]); + new_tasks.push_back(cp->instance.task_names[pi]); + } + cp->instance.device_names = std::move(new_devs); + cp->instance.task_names = std::move(new_tasks); + VLOG(1) << "Modified device_names on " << cp; +} + +// Establish the requested number of subdivision permutations based on the +// ring order implicit in the device order. +void GenerateSubdivPerms(const string& device, int source_rank, + CollectiveParams* cp) { + CHECK_GT(cp->instance.impl_details.subdiv_offsets.size(), 0); + cp->instance.impl_details.subdiv_permutations.resize( + cp->instance.impl_details.subdiv_offsets.size()); + // Each subdiv permutation is a ring formed by rotating each + // single-task subsequence of devices by an offset. This makes most + // sense when each task has the same number of devices but we can't + // depend on that being the case so we'll compute something that + // works in any case. + + // Start by counting the devices in each task. + // Precondition: device_names must be sorted so that all devices in + // the same task are adjacent. + VLOG(2) << "Sorted task names: " + << str_util::Join(cp->instance.task_names, ", "); + std::vector dev_per_task; + const string* prior_task_name = &cp->instance.task_names[0]; + int dev_count = 1; + for (int di = 1; di < cp->group.group_size; ++di) { + if (cp->instance.task_names[di] != *prior_task_name) { + dev_per_task.push_back(dev_count); + dev_count = 1; + prior_task_name = &cp->instance.task_names[di]; + } else { + ++dev_count; + } + } + dev_per_task.push_back(dev_count); + CHECK_EQ(cp->group.num_tasks, dev_per_task.size()); + + // Generate a ring permutation for each requested offset. + CHECK_GT(cp->instance.impl_details.subdiv_offsets.size(), 0); + VLOG(2) << "Setting up perms for cp " << cp << " subdiv_permutations " + << &cp->instance.impl_details.subdiv_permutations; + cp->instance.impl_details.subdiv_permutations.resize( + cp->instance.impl_details.subdiv_offsets.size()); + cp->subdiv_rank.resize(cp->instance.impl_details.subdiv_offsets.size(), -1); + for (int sdi = 0; sdi < cp->instance.impl_details.subdiv_offsets.size(); + ++sdi) { + std::vector& perm = cp->instance.impl_details.subdiv_permutations[sdi]; + CHECK_EQ(perm.size(), 0); + int offset = cp->instance.impl_details.subdiv_offsets[sdi]; + int prior_dev_count = 0; + for (int ti = 0; ti < cp->group.num_tasks; ++ti) { + for (int di = 0; di < dev_per_task[ti]; ++di) { + int offset_di = (di + offset) % dev_per_task[ti]; + int permuted_di = prior_dev_count + offset_di; + perm.push_back(permuted_di); + if (cp->instance.device_names[prior_dev_count + di] == device) { + CHECK_EQ(prior_dev_count + di, cp->default_rank); + cp->subdiv_rank[sdi] = permuted_di; + } + } + prior_dev_count += dev_per_task[ti]; + } + CHECK_EQ(cp->group.group_size, perm.size()); + } + + if (cp->instance.type == BROADCAST_COLLECTIVE) { + CHECK_GE(source_rank, 0); + cp->subdiv_source_rank.resize( + cp->instance.impl_details.subdiv_offsets.size(), -1); + for (int sdi = 0; sdi < cp->subdiv_source_rank.size(); ++sdi) { + for (int j = 0; j < cp->group.group_size; ++j) { + if (cp->instance.impl_details.subdiv_permutations[sdi][j] == + source_rank) { + cp->subdiv_source_rank[sdi] = j; + break; + } + } + CHECK_GE(cp->subdiv_source_rank[sdi], 0); + } + } + + if (VLOG_IS_ON(1)) { + // Log the computed ring order for each subdiv. + string buf; + for (int sdi = 0; + sdi < cp->instance.impl_details.subdiv_permutations.size(); ++sdi) { + buf = strings::StrCat("Subdiv ", sdi, " device order:\n"); + for (int di = 0; + di < cp->instance.impl_details.subdiv_permutations[sdi].size(); + ++di) { + int idx = cp->instance.impl_details.subdiv_permutations[sdi][di]; + strings::StrAppend(&buf, cp->instance.device_names[idx], "\n"); + } + strings::StrAppend(&buf, " subdiv_offsets: "); + for (auto o : cp->instance.impl_details.subdiv_offsets) + strings::StrAppend(&buf, o, " "); + strings::StrAppend(&buf, " SubdivRank: "); + for (auto d : cp->subdiv_rank) strings::StrAppend(&buf, d, " "); + VLOG(1) << buf; + } + } +} + +} // namespace + +void CollectiveParamResolverLocal::CompleteTaskIsLocal(const string& task_name, + CollectiveParams* cp) { + cp->task.is_local.resize(cp->group.group_size, false); + for (int i = 0; i < cp->group.group_size; ++i) { + cp->task.is_local[i] = (cp->instance.task_names[i] == task_name); + } +} + +void CollectiveParamResolverLocal::SetDefaultRank(const string& device, + CollectiveParams* cp) { + CHECK_EQ(cp->group.group_size, cp->instance.device_names.size()) << cp; + for (int i = 0; i < cp->group.group_size; ++i) { + if (cp->instance.device_names[i] == device) { + cp->default_rank = i; + break; + } + } +} + +Status CollectiveParamResolverLocal::InitInstanceSharedParams( + GroupRec* gr, const CollectiveParams* cp, InstanceRec* ir) { + VLOG(1) << "InitInstanceSharedParams " << ir; + ir->shared.instance = cp->instance; + { + mutex_lock gl(gr->mu); + ir->shared.group = gr->group; + ir->shared.instance.device_names.assign(gr->device_list.begin(), + gr->device_list.end()); + ir->shared.instance.task_names.assign(gr->task_list.begin(), + gr->task_list.end()); + VLOG(2) << "Initialized names for instance: " + << ir->shared.instance.ToString(); + } + ir->shared.default_rank = -1; + + // Sort devce_names lexicographcally, keeping task_names in + // corresponding order. + SortDevicesAndTasks(&ir->shared); + + // Get Locality data for all devices. + + // Set is_local and task_names in *shared prior to invoking + // GetDeviceLocalitiesAsync. In a distributed context this function can be + // called by a derived class, some of the devices may be non-local and + // GetDeviceLocalitiesAsync will use those fields to launch RPCs. + CompleteTaskIsLocal(task_name_, &ir->shared); + std::vector localities; + Notification note; + Status status; + dev_resolver_->GetDeviceLocalitiesAsync(ir->shared.instance, &localities, + [¬e, &status](const Status& s) { + status = s; + note.Notify(); + }); + note.WaitForNotification(); + if (status.ok()) { + CompleteDefaultRanking(gr, cp, ir, localities); + } + return status; +} + +void CollectiveParamResolverLocal::CompleteDefaultRanking( + GroupRec* gr, const CollectiveParams* cp, InstanceRec* ir, + const std::vector& localities) { + // Establish an instance-specific default rank order for devices + // based on localities. This rank order should be a good ring + // order, if possible. + GlobalDeviceMap gdm = EstablishGlobalRank(&ir->shared, localities); + // Reflect the new global ranking on shared + size_t num_devices = ir->shared.group.group_size; + std::vector new_device_names(num_devices, ""); + std::vector new_task_names(num_devices, ""); + for (const auto& git : gdm) { + const TaskDeviceMap& tdm = git.second; + for (const auto& tit : tdm) { + const DevRec& dr = tit.second; + new_device_names[dr.global_rank] = + ir->shared.instance.device_names[dr.original_rank]; + new_task_names[dr.global_rank] = + ir->shared.instance.task_names[dr.original_rank]; + } + } + + ir->shared.instance.device_names = new_device_names; + ir->shared.instance.task_names = new_task_names; + if (VLOG_IS_ON(2)) { + string buf; + for (const auto& d : cp->instance.device_names) + strings::StrAppend(&buf, "\n", d); + VLOG(2) << "Optimized device order for " << ir->shared.name << ": " << buf; + } +} + +void CollectiveParamResolverLocal::CallbackWithStatus( + const InstanceRecCallback& done, InstanceRec* irec) { + Status s; + { + mutex_lock l(irec->out_mu); + s = irec->status; + } + done(s, irec); +} + +void CollectiveParamResolverLocal::FindInstanceRec( + GroupRec* gr, CollectiveParams* cp, const InstanceRecCallback& done) { + InstanceRec* irec = nullptr; + bool exit_outside_locks = false; + { + mutex_lock l(instance_mu_); + auto it = instance_table_.find(cp->instance.instance_key); + if (it != instance_table_.end()) { + irec = it->second.get(); + { + mutex_lock l(irec->in_mu); + if (irec->is_init) { + exit_outside_locks = true; + } else { + irec->init_waiters.push_back([this, gr, cp, done](InstanceRec* irec) { + CallbackWithStatus(done, irec); + }); + return; + } + } + } else { + // Create new InstanceRec. + irec = new InstanceRec; + instance_table_[cp->instance.instance_key].reset(irec); + } + } + if (exit_outside_locks) { + CallbackWithStatus(done, irec); + return; + } + // Initialize the new InstanceRec while holding out_mu. + { + mutex_lock il(irec->out_mu); + irec->known.resize(cp->group.group_size, false); + irec->status = InitInstanceSharedParams(gr, cp, irec); + } + // Prepare to invoke any waiters that accumlated during initialization. + std::vector init_waiters; + { + mutex_lock tl(instance_mu_); + { + mutex_lock l(irec->in_mu); + irec->is_init = true; + if (!irec->init_waiters.empty()) { + std::swap(init_waiters, irec->init_waiters); + } + } + } + CallbackWithStatus(done, irec); + for (auto& f : init_waiters) { + f(irec); + } +} + +void CollectiveParamResolverLocal::CompleteParamsAsync( + const string& device, CollectiveParams* cp, CancellationManager* cancel_mgr, + const StatusCallback& done) { + VLOG(1) << "CompleteParams " << device << " for " << cp << ": " + << cp->ToString(); + CompleteGroupLocal( + device, cp, [this, device, cp, done](const Status& s, GroupRec* gr) { + if (s.ok()) { + CompleteInstanceLocal(device, gr, cp, cp->is_source, done); + } else { + done(s); + } + }); +} + +void CollectiveParamResolverLocal::CompleteInstanceAsync( + const CompleteInstanceRequest* request, CompleteInstanceResponse* response, + CancellationManager* cancel_mgr, const StatusCallback& done) { + done( + errors::Internal("CompleteInstance is not implemented by " + "CollectiveParamResolverLocal which is " + "intended only for non-distributed deployment.")); +} + +void CollectiveParamResolverLocal::CompleteInstanceLocal( + const string& device, GroupRec* gr, CollectiveParams* cp, bool is_source, + const StatusCallback& done) { + VLOG(1) << "CompleteInstanceLocal " << device + << " instance_key: " << cp->instance.instance_key << " gr " << gr; + + // Populate the group portion of *cp from *gr. Most of it should already + // match. + DCHECK_EQ(cp->group.group_key, gr->group.group_key); + DCHECK_EQ(cp->group.group_size, gr->group.group_size); + DCHECK_EQ(cp->group.device_type, gr->group.device_type); + cp->group = gr->group; + + // Get the shared InstanceRec for this instance. + FindInstanceRec(gr, cp, + [this, device, gr, cp, is_source, done](const Status& s, + InstanceRec* ir) { + if (s.ok()) { + CompleteInstanceFromInitializedIRec(device, gr, cp, ir, + is_source, done); + } else { + done(s); + } + }); +} + +void CollectiveParamResolverLocal::CompleteInstanceFromInitializedIRec( + const string& device, GroupRec* gr, CollectiveParams* cp, InstanceRec* ir, + bool is_source, const StatusCallback& done) { + // Populate the fields common across instance. + { + mutex_lock l(ir->out_mu); + // custom operator= does a deep copy. + cp->instance = ir->shared.instance; + } + // Populate the fields common across task, also default_rank. + SetDefaultRank(device, cp); + CompleteTaskIsLocal(task_name_, cp); + // If broadcast, may need to wait for source discovery. + if (cp->instance.type == BROADCAST_COLLECTIVE) { + CompleteInstanceSource(ir, cp, is_source, + [this, ir, device, cp, done](InstanceRec* irec) { + CHECK_EQ(ir, irec); + Status s; + int source_rank; + { + mutex_lock l(irec->out_mu); + s = irec->status; + source_rank = ir->source_rank; + } + if (s.ok()) { + GenerateSubdivPerms(device, source_rank, cp); + } + done(s); + }); + return; + } else { + GenerateSubdivPerms(device, 0, cp); + } + done(Status::OK()); +} + +void CollectiveParamResolverLocal::CompleteInstanceSource(InstanceRec* ir, + CollectiveParams* cp, + bool is_source, + const IRConsumer& f) { + std::vector ready_waiters; + { + mutex_lock l(ir->out_mu); + CHECK_EQ(cp->group.group_size, ir->known.size()); + CHECK_GE(cp->default_rank, 0); + if (!ir->known[cp->default_rank]) { + ir->known[cp->default_rank] = true; + ++ir->known_count; + if (is_source) { + if (ir->source_rank >= 0) { + ir->status = errors::Internal("Instance ", cp->instance.instance_key, + " already has source ", ir->source_rank, + ", recevied second claim from ", + cp->default_rank); + } else { + ir->source_rank = cp->default_rank; + } + } + } + if (ir->known_count < ir->shared.group.group_size) { + ir->known_waiters.push_back(f); + return; + } + CHECK_EQ(ir->known_count, ir->shared.group.group_size); + CHECK_GE(ir->source_rank, 0); + if (!ir->known_waiters.empty()) { + ready_waiters = std::move(ir->known_waiters); + } + } + f(ir); + for (auto& f : ready_waiters) { + f(ir); + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.h b/tensorflow/core/common_runtime/collective_param_resolver_local.h new file mode 100644 index 0000000000..ff3415b0a9 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_param_resolver_local.h @@ -0,0 +1,209 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_PARAM_RESOLVER_LOCAL_H_ +#define TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_PARAM_RESOLVER_LOCAL_H_ + +#include + +#include "tensorflow/core/framework/collective.h" +#include "tensorflow/core/lib/gtl/flatmap.h" + +namespace tensorflow { +class CompleteGroupRequest; +class CompleteGroupResponse; +class CompleteInstanceRequest; +class CompleteInstanceResponse; +class DeviceMgr; + +// Implements ParamResolverInterface for a single-task context. +// It also implements the functionality necessary to serve as the +// group leader for param resolution in a multi-task context. +class CollectiveParamResolverLocal : public ParamResolverInterface { + public: + CollectiveParamResolverLocal(const DeviceMgr* dev_mgr, + DeviceResolverInterface* dev_resolver, + const string& task_name); + + ~CollectiveParamResolverLocal() override {} + + void CompleteParamsAsync(const string& device, CollectiveParams* cp, + CancellationManager* cancel_mgr, + const StatusCallback& done) override; + + void CompleteGroupAsync(const CompleteGroupRequest* request, + CompleteGroupResponse* response, + CancellationManager* cancel_mgr, + const StatusCallback& done) override; + + void CompleteInstanceAsync(const CompleteInstanceRequest* request, + CompleteInstanceResponse* response, + CancellationManager* cancel_mgr, + const StatusCallback& done) override; + + protected: + // Used to complete/verify CollGroup. + struct GroupRec { + CollGroupParams group; + mutex mu; + Status status GUARDED_BY(mu); + std::set device_set GUARDED_BY(mu); + std::vector device_list GUARDED_BY(mu); + std::set task_set GUARDED_BY(mu); + std::vector task_list GUARDED_BY(mu); + std::vector waiting GUARDED_BY(mu); + }; + + // Finds the GroupRec that corresponds to cp->group_key. + // Also populates cp->group from that group_rec. + // Will wait until GroupRec is fully populated or an error arises before + // calling done. Callback GroupRec* arg is only valid if status is ok. + // Ownership of GroupRec stays with this object and does not pass to the + // callback. + typedef std::function GroupRecCallback; + void CompleteGroupLocal(const string& device, CollectiveParams* cp, + const GroupRecCallback& done) + LOCKS_EXCLUDED(group_mu_); + + // Used to complete/verify CollInstance. + struct InstanceRec; + typedef std::function IRConsumer; + struct InstanceRec { + // This structure has two mutexes so that a possibly long + // initialization can be done without holding the instance_mu_ + // table lock the whole time (which can cause an excessive number + // of threads to block on it), and because the compiler may not + // permit mutex locks to be taken in more than one order. + // + // out_mu guards access to most of the fields. + // in_mu guards access to a queue of comsumer callbacks wanting to + // read the fields guarded by out_mu. + // + // The in_mu should be locked only while holding instance_mu_; the + // out_mu should be locked only while not holding + // instance_mu_. + // + // When is_init is false (the initial value) any potential user + // other than the creator should queue a callback on init_waiters. + // As soon as the shared member of this structure is fully + // initialized is_init will be set true and those callbacks will + // be invoked. + // + // Once inserted in the table this structure will never be replaced + // so users can capture the pointer while holding instance_mu_, + // drop that lock, then take a lock on out_mu before + // reading/modifying its values. + mutex in_mu; + bool is_init GUARDED_BY(in_mu); + std::vector init_waiters GUARDED_BY(in_mu); + + // Values to be shared by all instances, constant after initialization. + mutex out_mu; + CollectiveParams shared GUARDED_BY(out_mu); + // If an error occurs during initialization this structure stays in + // the table with a non-OK status. Purging the table and restarting + // needs to be done at a higher level. + Status status GUARDED_BY(out_mu); + + // These fields are used to count the instances that have called + // in and become known while resolving broadcast source identity. + int source_rank GUARDED_BY(out_mu); + int known_count GUARDED_BY(out_mu); + std::vector known GUARDED_BY(out_mu); + std::vector known_waiters GUARDED_BY(out_mu); + + InstanceRec() : is_init(false), source_rank(-1), known_count(0) {} + }; + + // Find the InstanceRec with the same instance_key as cp. If it doesn't + // already exist, create and initialize from gr and cp. + // + // Precondition: *gr must be a complete GroupRec, i.e. the value set + // by CompleteGroupLocal. *cp must be populated with all the fields + // required by InitInstanceSharedParams. Ownership of InstanceRec stays + // with this object and does not pass to the callback. + typedef std::function + InstanceRecCallback; + void FindInstanceRec(GroupRec* gr, CollectiveParams* cp, + const InstanceRecCallback& done) + LOCKS_EXCLUDED(instance_mu_, gr->mu, group_mu_); + + // Populate *ir with device membership from gr, then initialize to be specific + // to cp->instance_key, i.e. order the devices and tasks. + // + // Preconditions: + // cp is populated with all DeviceLocalities + Status InitInstanceSharedParams(GroupRec* gr, const CollectiveParams* cp, + InstanceRec* ir) + EXCLUSIVE_LOCKS_REQUIRED(ir->out_mu) LOCKS_EXCLUDED(gr->mu); + + // Establishes the final order of ir->shared.instance.device_names and + // ir->shared.instance.task_names by considering localities of all devices. + void CompleteDefaultRanking(GroupRec* gr, const CollectiveParams* cp, + InstanceRec* ir, + const std::vector& localities) + EXCLUSIVE_LOCKS_REQUIRED(ir->out_mu); + + // Finish populating *cp. + // Precondition: *gr has been fully populated by CompleteGroupLocal. + void CompleteInstanceLocal(const string& device, GroupRec* gr, + CollectiveParams* cp, bool is_source, + const StatusCallback& done) + LOCKS_EXCLUDED(instance_mu_, gr->mu, group_mu_); + + // Finish populating *cp from fully initialized *ir. + // Precondition: *gr and *ir are fully populated. + void CompleteInstanceFromInitializedIRec(const string& device, GroupRec* gr, + CollectiveParams* cp, + InstanceRec* ir, bool is_source, + const StatusCallback& done) + LOCKS_EXCLUDED(ir->out_mu); + + // Complete source data for a broadcast instance. + // Precondition: *cp has complete group data and default_rank. + void CompleteInstanceSource(InstanceRec* ir, CollectiveParams* cp, + bool is_source, const IRConsumer& f) + LOCKS_EXCLUDED(ir->out_mu); + + // If cp.device_names contains only devices local to this process + // populates *localities, else returns an error. + Status GetLocalDeviceLocalities(const CollectiveParams& cp, + std::vector* localities); + + // Sets CollTaskParams.is_local and CollectiveParams.default_rank. + // Precondition: cp->device_names is fully populated and in final order. + void CompleteTaskIsLocal(const string& task_name, CollectiveParams* cp); + + // Sets cp->instance_default_rank according to location of device in + // current ordering of cp->instance.device_names. + void SetDefaultRank(const string& device, CollectiveParams* cp); + + // Helper to grab status under lock, invoke callback out of lock. + void CallbackWithStatus(const InstanceRecCallback& done, InstanceRec* irec) + LOCKS_EXCLUDED(irec->out_mu); + + const DeviceMgr* dev_mgr_; + DeviceResolverInterface* dev_resolver_; + string task_name_; + mutex group_mu_; + gtl::FlatMap> group_table_ + GUARDED_BY(group_mu_); + mutex instance_mu_; + gtl::FlatMap> instance_table_ + GUARDED_BY(instance_mu_); +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_PARAM_RESOLVER_LOCAL_H_ diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc b/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc new file mode 100644 index 0000000000..4e3c7125f2 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc @@ -0,0 +1,151 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_executor_mgr.h" + +#include "tensorflow/core/common_runtime/collective_param_resolver_local.h" +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/common_runtime/device_resolver_local.h" +#include "tensorflow/core/lib/core/notification.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" + +namespace tensorflow { +namespace { + +#define NUM_DEVS 3 + +class CollectiveParamResolverLocalTest : public ::testing::Test { + protected: + CollectiveParamResolverLocalTest() { + ConfigProto cp; + SessionOptions options; + string task_name = "/job:localhost/replica:0/task:0"; + auto* device_count = options.config.mutable_device_count(); + device_count->insert({"CPU", NUM_DEVS}); + TF_CHECK_OK(DeviceFactory::AddDevices(options, task_name, &devices_)); + device_mgr_.reset(new DeviceMgr(devices_)); + drl_.reset(new DeviceResolverLocal(device_mgr_.get())); + prl_.reset(new CollectiveParamResolverLocal(device_mgr_.get(), drl_.get(), + task_name)); + } + + std::vector devices_; + std::unique_ptr device_mgr_; + std::unique_ptr drl_; + std::unique_ptr prl_; +}; + +TEST_F(CollectiveParamResolverLocalTest, CompleteParamsReduction1Task) { + CollectiveParams cps[NUM_DEVS]; + Status statuses[NUM_DEVS]; + Notification note[NUM_DEVS]; + for (int i = 0; i < NUM_DEVS; ++i) { + CollectiveParams* cp = &cps[i]; + cp->group.group_key = 1; + cp->group.group_size = 3; + cp->group.device_type = DeviceType("CPU"); + cp->group.num_tasks = 1; + cp->instance.instance_key = 7; + cp->instance.type = REDUCTION_COLLECTIVE; + cp->instance.data_type = DataType(DT_FLOAT); + cp->instance.shape = TensorShape({5}); + cp->instance.device_names.push_back( + strings::StrCat("/job:localhost/replica:0/task:0/device:CPU:", i)); + cp->instance.impl_details.subdiv_offsets.push_back(0); + cp->is_source = false; + Env::Default()->SchedClosure([this, i, cp, ¬e, &statuses]() { + prl_->CompleteParamsAsync(cp->instance.device_names[0], cp, + nullptr /*CancellationManager*/, + [this, &statuses, ¬e, i](const Status& s) { + statuses[i] = s; + note[i].Notify(); + }); + }); + } + for (int i = 0; i < NUM_DEVS; ++i) { + note[i].WaitForNotification(); + } + for (int i = 0; i < NUM_DEVS; ++i) { + TF_ASSERT_OK(statuses[i]); + ASSERT_EQ(cps[i].instance.device_names.size(), 3); + for (int j = 0; j < NUM_DEVS; ++j) { + EXPECT_EQ( + strings::StrCat("/job:localhost/replica:0/task:0/device:CPU:", j), + cps[i].instance.device_names[j]); + EXPECT_TRUE(cps[i].task.is_local[j]); + } + EXPECT_EQ(cps[i].subdiv_rank[0], i); + EXPECT_EQ(cps[i].subdiv_source_rank.size(), 0); + EXPECT_FALSE(cps[i].is_source); + EXPECT_EQ(cps[i].default_rank, i); + } +} + +TEST_F(CollectiveParamResolverLocalTest, CompleteParamsBroadcast1Task) { + CollectiveParams cps[NUM_DEVS]; + Status statuses[NUM_DEVS]; + Notification note[NUM_DEVS]; + for (int i = 0; i < NUM_DEVS; ++i) { + CollectiveParams* cp = &cps[i]; + cp->group.group_key = 1; + cp->group.group_size = 3; + cp->group.device_type = DeviceType("CPU"); + cp->group.num_tasks = 1; + cp->instance.instance_key = 3; + cp->instance.type = BROADCAST_COLLECTIVE; + cp->instance.data_type = DataType(DT_FLOAT); + cp->instance.shape = TensorShape({5}); + cp->instance.device_names.push_back( + strings::StrCat("/job:localhost/replica:0/task:0/device:CPU:", i)); + cp->instance.impl_details.subdiv_offsets.push_back(0); + cp->is_source = (i == 1); + Env::Default()->SchedClosure([this, i, cp, ¬e, &statuses]() { + prl_->CompleteParamsAsync(cp->instance.device_names[0], cp, + nullptr /*CancellationManager*/, + [this, &statuses, ¬e, i](const Status& s) { + statuses[i] = s; + note[i].Notify(); + }); + }); + } + for (int i = 0; i < NUM_DEVS; ++i) { + note[i].WaitForNotification(); + } + for (int i = 0; i < NUM_DEVS; ++i) { + TF_ASSERT_OK(statuses[i]); + ASSERT_EQ(cps[i].instance.device_names.size(), 3); + for (int j = 0; j < NUM_DEVS; ++j) { + EXPECT_EQ( + strings::StrCat("/job:localhost/replica:0/task:0/device:CPU:", j), + cps[i].instance.device_names[j]); + EXPECT_TRUE(cps[i].task.is_local[j]); + } + ASSERT_GT(cps[i].subdiv_rank.size(), 0); + EXPECT_EQ(cps[i].subdiv_rank[0], i); + ASSERT_GT(cps[i].subdiv_source_rank.size(), 0); + EXPECT_EQ(cps[i].subdiv_source_rank[0], 1); + EXPECT_EQ(cps[i].is_source, (i == 1)); + EXPECT_EQ(cps[i].default_rank, i); + } +} + +// TEST_F(CollectiveParamResolverLocalTest, + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_rma_local.cc b/tensorflow/core/common_runtime/collective_rma_local.cc new file mode 100644 index 0000000000..ad9b32ce35 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_rma_local.cc @@ -0,0 +1,108 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_rma_local.h" + +#include "tensorflow/core/common_runtime/copy_tensor.h" +#include "tensorflow/core/common_runtime/dma_helper.h" + +namespace tensorflow { + +void CollectiveRemoteAccessLocal::StartAbort(const Status& s) { + buf_rendezvous_.StartAbort(s); +} + +void CollectiveRemoteAccessLocal::RecvFromPeer( + const string& peer_device, const string& peer_task, bool peer_is_local, + const string& key, Device* to_device, DeviceContext* to_device_ctx, + const AllocatorAttributes& to_alloc_attr, Tensor* to_tensor, + const DeviceLocality& client_locality, const StatusCallback& done) { + VLOG(1) << "RecvFromPeer " << this << " from " << peer_device << " key " + << key; + if (!peer_is_local) { + done( + errors::Internal("CollectiveRemoteAccessLocal::RecvFromPeer " + "called with peer_is_local=false")); + return; + } + buf_rendezvous_.ConsumeBuf( + key, [this, to_tensor, to_device_ctx, to_device, to_alloc_attr, done]( + const Status& s, BufRendezvous::Hook* hook) { + if (!s.ok()) { + done(s); + delete hook; + } else { + int64 recv_bytes = to_tensor->TotalBytes(); + CHECK_EQ(recv_bytes, hook->prod_value->TotalBytes()); + MemCpyAsync(hook->prod_ctx, // src DeviceContext + to_device_ctx, // dst DeviceContext + hook->prod_dev, // src Device + to_device, // dst Device + hook->prod_attr, // src AllocatorAttributes + to_alloc_attr, // dst AllocatorAttributes + hook->prod_value, // src Tensor* + to_tensor, // dst Tensor* + [hook, done](const Status& s) { + done(s); + hook->prod_cb(s); + delete hook; + }); + } + }); +} + +void CollectiveRemoteAccessLocal::PostToPeer( + const string& peer_device, const string& peer_task, const string& key, + Device* from_device, DeviceContext* from_device_ctx, + const AllocatorAttributes& from_alloc_attr, const Tensor* from_tensor, + const DeviceLocality& client_locality, const StatusCallback& done) { + VLOG(1) << "PostToPeer " << this << " key " << key + << " step_id_=" << step_id_; + buf_rendezvous_.ProvideBuf(key, from_device, from_device_ctx, from_tensor, + from_alloc_attr, done); +} + +/*static*/ +void CollectiveRemoteAccessLocal::MemCpyAsync( + DeviceContext* src_dev_ctx, DeviceContext* dst_dev_ctx, Device* src_dev, + Device* dst_dev, const AllocatorAttributes& src_attr, + const AllocatorAttributes& dst_attr, const Tensor* src, Tensor* dst, + const StatusCallback& done) { + // We want a real copy to happen, i.e. the bytes inside of src should be + // transferred to the buffer backing dst. If src and dst are on different + // devices then CopyTensor::ViaDMA will do just that. But if they're both + // the same CPU, then it will actually just reset dst to point to src. + // Since this routine is used for copying between devices and within a + // device, we need to detect and bypass the wrong-semantics case. + const DeviceType src_device_type( + src_attr.on_host() ? DEVICE_CPU : src_dev->attributes().device_type()); + const DeviceType dst_device_type( + dst_attr.on_host() ? DEVICE_CPU : dst_dev->attributes().device_type()); + const bool non_cpu_src = src_device_type != DeviceType(DEVICE_CPU); + const bool non_cpu_dst = dst_device_type != DeviceType(DEVICE_CPU); + if (non_cpu_src) CHECK(src_dev_ctx); + if (non_cpu_dst) CHECK(dst_dev_ctx); + if (non_cpu_src || non_cpu_dst) { + CopyTensor::ViaDMA("", // edge name (non-existent) + src_dev_ctx, dst_dev_ctx, src_dev, dst_dev, src_attr, + dst_attr, src, dst, done); + } else { + int64 bytes = src->TotalBytes(); + DCHECK_EQ(dst->TotalBytes(), bytes); + memcpy(DMAHelper::base(dst), DMAHelper::base(src), bytes); + done(Status::OK()); + } +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/collective_rma_local.h b/tensorflow/core/common_runtime/collective_rma_local.h new file mode 100644 index 0000000000..d25dd5f04a --- /dev/null +++ b/tensorflow/core/common_runtime/collective_rma_local.h @@ -0,0 +1,88 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_RMA_LOCAL_ACCESS_H_ +#define TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_RMA_LOCAL_ACCESS_H_ +#include "tensorflow/core/common_runtime/buf_rendezvous.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/framework/collective.h" +#include "tensorflow/core/framework/rendezvous.h" + +namespace tensorflow { + +// Basic implementation of PerStepCollectiveRemoteAccess. +class CollectiveRemoteAccessLocal : public PerStepCollectiveRemoteAccess { + public: + CollectiveRemoteAccessLocal(const DeviceMgr* dev_mgr, + DeviceResolverInterface* dev_resolver, + int64 step_id) + : dev_mgr_(dev_mgr), + dev_resolver_(dev_resolver), + buf_rendezvous_(step_id), + step_id_(step_id) {} + + virtual ~CollectiveRemoteAccessLocal() {} + + void StartAbort(const Status& s); + + void RecvFromPeer(const string& peer_device, const string& peer_task, + bool peer_is_local, const string& key, Device* to_device, + DeviceContext* to_device_ctx, + const AllocatorAttributes& to_alloc_attr, Tensor* to_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) override; + + void PostToPeer(const string& peer_device, const string& peer_task, + const string& key, Device* from_device, + DeviceContext* from_device_ctx, + const AllocatorAttributes& from_alloc_attr, + const Tensor* from_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) override; + + void GetDeviceLocalitiesAsync(const CollInstanceParams& ci_params, + std::vector* localities, + const StatusCallback& done) override { + dev_resolver_->GetDeviceLocalitiesAsync(ci_params, localities, done); + } + + void GetLocalityAsync(const string& device, const string& task, + DeviceLocality* locality, + const StatusCallback& done) override { + dev_resolver_->GetLocalityAsync(device, task, locality, done); + } + + void ClearTask(const string& task) override { + dev_resolver_->ClearTask(task); + } + + // Copy utility that always copies bytes from src to dst even if + // they are on the same device, unlike CopyTensor::ViaDMA which will + // just change the dst buffer pointer in that case. + static void MemCpyAsync(DeviceContext* src_dev_ctx, + DeviceContext* dst_dev_ctx, Device* src_dev, + Device* dst_dev, const AllocatorAttributes& src_attr, + const AllocatorAttributes& dst_attr, + const Tensor* src, Tensor* dst, + const StatusCallback& done); + + protected: + const DeviceMgr* dev_mgr_; // not owned + DeviceResolverInterface* dev_resolver_; // not owned + BufRendezvous buf_rendezvous_; + int64 step_id_; +}; + +} // namespace tensorflow +#endif // TENSORFLOW_COMMON_RUNTIME_COLLECTIVE_RMA_LOCAL_ACCESS_H_ diff --git a/tensorflow/core/common_runtime/collective_rma_local_test.cc b/tensorflow/core/common_runtime/collective_rma_local_test.cc new file mode 100644 index 0000000000..dcd4272d96 --- /dev/null +++ b/tensorflow/core/common_runtime/collective_rma_local_test.cc @@ -0,0 +1,148 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/collective_rma_local.h" + +#include "tensorflow/core/common_runtime/buf_rendezvous.h" +#include "tensorflow/core/common_runtime/collective_param_resolver_local.h" +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/common_runtime/device_resolver_local.h" +#include "tensorflow/core/common_runtime/dma_helper.h" +#include "tensorflow/core/lib/core/notification.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" + +namespace tensorflow { +namespace { + +#define NUM_DEVS 3 +static const int kStepId = 123; + +class CollectiveRemoteAccessLocalTest : public ::testing::Test { + protected: + const string kTaskName = "/job:localhost/replica:0/task:0"; + + CollectiveRemoteAccessLocalTest() { + ConfigProto cp; + SessionOptions options; + auto* device_count = options.config.mutable_device_count(); + device_count->insert({"CPU", NUM_DEVS}); + TF_CHECK_OK(DeviceFactory::AddDevices(options, kTaskName, &devices_)); + device_mgr_.reset(new DeviceMgr(devices_)); + drl_.reset(new DeviceResolverLocal(device_mgr_.get())); + prl_.reset(new CollectiveParamResolverLocal(device_mgr_.get(), drl_.get(), + kTaskName)); + rma_.reset(new CollectiveRemoteAccessLocal(device_mgr_.get(), drl_.get(), + kStepId)); + } + + std::vector devices_; + std::unique_ptr device_mgr_; + std::unique_ptr drl_; + std::unique_ptr prl_; + std::unique_ptr rma_; +}; + +TEST_F(CollectiveRemoteAccessLocalTest, PostRecvCPU0) { + Device* cpu0 = nullptr; + AllocatorAttributes attr; + DeviceLocality dev_locality; + TF_ASSERT_OK(device_mgr_->LookupDevice(kTaskName + "/device:CPU:0", &cpu0)); + Tensor sink_tensor(DT_FLOAT, TensorShape({8})); + Notification recv_note; + Status recv_status; + rma_->RecvFromPeer(kTaskName + "/device:CPU:0", kTaskName, true /*is_local*/, + "key_0", cpu0 /*to_device*/, nullptr /*to_device_ctx*/, + attr /*to_alloc_attr*/, &sink_tensor, dev_locality, + [this, &recv_note, &recv_status](const Status& s) { + recv_status = s; + recv_note.Notify(); + }); + Tensor source_tensor(DT_FLOAT, TensorShape({8})); + for (int i = 0; i < 8; ++i) { + source_tensor.flat()(i) = i / 2; + } + // Tensors have distinct storage. + EXPECT_NE(DMAHelper::base(&source_tensor), DMAHelper::base(&sink_tensor)); + Notification send_note; + Status send_status; + rma_->PostToPeer(kTaskName + "/device:CPU:0", kTaskName, "key_0", + cpu0 /*from_device*/, nullptr /*from_device_ctx*/, + attr /*to_alloc_attr*/, &source_tensor, dev_locality, + [this, &send_note, &send_status](const Status& s) { + send_status = s; + send_note.Notify(); + }); + recv_note.WaitForNotification(); + send_note.WaitForNotification(); + TF_EXPECT_OK(recv_status); + TF_EXPECT_OK(send_status); + // Sink tensor gets the source tensor values. + for (int i = 0; i < 8; ++i) { + EXPECT_EQ(sink_tensor.flat()(i), i / 2); + } + // And still has distinct storage. + EXPECT_NE(DMAHelper::base(&source_tensor), DMAHelper::base(&sink_tensor)); +} + +TEST_F(CollectiveRemoteAccessLocalTest, PostRecvCPU1_2) { + Device* cpu2 = nullptr; + AllocatorAttributes attr; + DeviceLocality dev_locality; + TF_ASSERT_OK(device_mgr_->LookupDevice(kTaskName + "/device:CPU:2", &cpu2)); + Tensor sink_tensor(DT_FLOAT, TensorShape({8})); + Notification recv_note; + Status recv_status; + rma_->RecvFromPeer(kTaskName + "/device:CPU:1", kTaskName, true /*is_local*/, + "key_0", cpu2 /*to_device*/, nullptr /*to_device_ctx*/, + attr /*to_alloc_attr*/, &sink_tensor, dev_locality, + [this, &recv_note, &recv_status](const Status& s) { + recv_status = s; + recv_note.Notify(); + }); + Tensor source_tensor(DT_FLOAT, TensorShape({8})); + for (int i = 0; i < 8; ++i) { + source_tensor.flat()(i) = i / 2; + } + // Tensors have distinct storage. + EXPECT_NE(DMAHelper::base(&source_tensor), DMAHelper::base(&sink_tensor)); + Device* cpu1 = nullptr; + TF_ASSERT_OK(device_mgr_->LookupDevice(kTaskName + "/device:CPU:1", &cpu1)); + Notification send_note; + Status send_status; + rma_->PostToPeer(kTaskName + "/device:CPU:2", kTaskName, "key_0", + cpu1 /*from_device*/, nullptr /*from_device_ctx*/, + attr /*to_alloc_attr*/, &source_tensor, dev_locality, + [this, &send_note, &send_status](const Status& s) { + send_status = s; + send_note.Notify(); + }); + recv_note.WaitForNotification(); + send_note.WaitForNotification(); + TF_EXPECT_OK(recv_status); + TF_EXPECT_OK(send_status); + // Sink tensor gets the source tensor values. + for (int i = 0; i < 8; ++i) { + EXPECT_EQ(sink_tensor.flat()(i), i / 2); + } + // And still has distinct storage. + EXPECT_NE(DMAHelper::base(&source_tensor), DMAHelper::base(&sink_tensor)); +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/device_resolver_local.cc b/tensorflow/core/common_runtime/device_resolver_local.cc new file mode 100644 index 0000000000..17ef4a2284 --- /dev/null +++ b/tensorflow/core/common_runtime/device_resolver_local.cc @@ -0,0 +1,49 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/device_resolver_local.h" + +#include "tensorflow/core/common_runtime/device_mgr.h" + +namespace tensorflow { + +void DeviceResolverLocal::GetDeviceLocalitiesAsync( + const CollInstanceParams& ci_params, + std::vector* localities, const StatusCallback& done) { + localities->clear(); + for (const string& device_name : ci_params.device_names) { + Device* dev; + Status s = dev_mgr_->LookupDevice(device_name, &dev); + if (!s.ok()) { + done(s); + return; + } + localities->push_back(dev->attributes().locality()); + } + done(Status::OK()); +} + +void DeviceResolverLocal::GetLocalityAsync(const string& device, + const string& task, + DeviceLocality* locality, + const StatusCallback& done) { + Device* dev; + Status s = dev_mgr_->LookupDevice(device, &dev); + if (s.ok()) { + *locality = dev->attributes().locality(); + } + done(s); +} + +} // namespace tensorflow diff --git a/tensorflow/core/common_runtime/device_resolver_local.h b/tensorflow/core/common_runtime/device_resolver_local.h new file mode 100644 index 0000000000..098eccdf84 --- /dev/null +++ b/tensorflow/core/common_runtime/device_resolver_local.h @@ -0,0 +1,48 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_COMMON_RUNTIME_DEVICE_RESOLVER_LOCAL_H_ +#define TENSORFLOW_COMMON_RUNTIME_DEVICE_RESOLVER_LOCAL_H_ + +#include + +#include "tensorflow/core/framework/collective.h" +#include "tensorflow/core/framework/device_attributes.pb.h" + +namespace tensorflow { +class DeviceMgr; + +// Implements DeviceResolverInterface in a single-task context. +class DeviceResolverLocal : public DeviceResolverInterface { + public: + DeviceResolverLocal(const DeviceMgr* dev_mgr) : dev_mgr_(dev_mgr) {} + + virtual ~DeviceResolverLocal() {} + + void GetDeviceLocalitiesAsync(const CollInstanceParams& ci_params, + std::vector* localities, + const StatusCallback& done) override; + + void GetLocalityAsync(const string& device, const string& task, + DeviceLocality* locality, + const StatusCallback& done) override; + + void ClearTask(const string& task) override {} + + protected: + const DeviceMgr* dev_mgr_; +}; + +} // namespace tensorflow +#endif // TENSORFLOW_COMMON_RUNTIME_DEVICE_RESOLVER_LOCAL_H_ diff --git a/tensorflow/core/common_runtime/device_resolver_local_test.cc b/tensorflow/core/common_runtime/device_resolver_local_test.cc new file mode 100644 index 0000000000..f5a6471ff7 --- /dev/null +++ b/tensorflow/core/common_runtime/device_resolver_local_test.cc @@ -0,0 +1,87 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/common_runtime/device_resolver_local.h" + +#include "tensorflow/core/common_runtime/device.h" +#include "tensorflow/core/common_runtime/device_factory.h" +#include "tensorflow/core/common_runtime/device_mgr.h" +#include "tensorflow/core/lib/core/notification.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/public/session_options.h" + +namespace tensorflow { +namespace { + +#define NUM_DEVS 3 + +class DeviceResolverLocalTest : public ::testing::Test { + protected: + DeviceResolverLocalTest() { + ConfigProto cp; + SessionOptions options; + string task_name = "/job:localhost/replica:0/task:0"; + auto* device_count = options.config.mutable_device_count(); + device_count->insert({"CPU", NUM_DEVS}); + TF_CHECK_OK(DeviceFactory::AddDevices(options, task_name, &devices_)); + device_mgr_.reset(new DeviceMgr(devices_)); + drl_.reset(new DeviceResolverLocal(device_mgr_.get())); + } + + std::vector devices_; + std::unique_ptr device_mgr_; + std::unique_ptr drl_; +}; + +TEST_F(DeviceResolverLocalTest, GetDeviceLocalitiesKnown) { + CollectiveParams cp; + std::vector localities; + cp.instance.device_names.push_back( + "/job:localhost/replica:0/task:0/device:CPU:1"); + cp.instance.device_names.push_back( + "/job:localhost/replica:0/task:0/device:CPU:2"); + Notification note; + Status status; + drl_->GetDeviceLocalitiesAsync(cp.instance, &localities, + [this, ¬e, &status](const Status& s) { + status = s; + note.Notify(); + }); + note.WaitForNotification(); + TF_EXPECT_OK(status); + EXPECT_EQ(2, localities.size()); +} + +TEST_F(DeviceResolverLocalTest, GetDeviceLocalitiesUnknown) { + CollectiveParams cp; + std::vector localities; + // In some builds there may be 1 GPU, but there should never be 9. + cp.instance.device_names.push_back( + "/job:localhost/replica:0/task:0/device:GPU:9"); + Notification note; + Status status; + drl_->GetDeviceLocalitiesAsync(cp.instance, &localities, + [this, ¬e, &status](const Status& s) { + status = s; + note.Notify(); + }); + note.WaitForNotification(); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(0, localities.size()); +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/framework/collective.cc b/tensorflow/core/framework/collective.cc new file mode 100644 index 0000000000..a26f2c2f31 --- /dev/null +++ b/tensorflow/core/framework/collective.cc @@ -0,0 +1,120 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include "tensorflow/core/framework/collective.h" + +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/hash/hash.h" +#include "tensorflow/core/lib/strings/strcat.h" + +namespace tensorflow { + +string CollGroupParams::ToString() const { + return strings::StrCat("CollGroupParams {group_key=", group_key, + " group_size=", group_size, + " device_type=", device_type.type_string(), + " num_tasks=", num_tasks, "}"); +} + +CollInstanceParams& CollInstanceParams::operator=( + const CollInstanceParams& other) { + if (this != &other) { + instance_key = other.instance_key; + type = other.type; + data_type = other.data_type; + shape = other.shape; + device_names.clear(); + device_names.assign(other.device_names.begin(), other.device_names.end()); + task_names.assign(other.task_names.begin(), other.task_names.end()); + impl_details.subdiv_offsets.assign( + other.impl_details.subdiv_offsets.begin(), + other.impl_details.subdiv_offsets.end()); + impl_details.subdiv_permutations.clear(); + for (auto p : other.impl_details.subdiv_permutations) { + impl_details.subdiv_permutations.push_back( + std::vector(p.begin(), p.end())); + } + impl_details.subdiv_source_rank.assign( + other.impl_details.subdiv_source_rank.begin(), + other.impl_details.subdiv_source_rank.end()); + } + return *this; +} + +string CollInstanceParams::ToString() const { + string v = strings::StrCat("CollInstanceParams { instance_key=", instance_key, + " type=", type, " data_type=", data_type, + " shape=", shape.DebugString(), " devices {"); + for (const auto& d : device_names) { + strings::StrAppend(&v, d, ","); + } + strings::StrAppend(&v, "} task_names={"); + for (const auto& n : task_names) { + strings::StrAppend(&v, n, ", "); + } + strings::StrAppend(&v, "}, subdiv_offsets={"); + for (const auto& d : impl_details.subdiv_offsets) { + strings::StrAppend(&v, d, ","); + } + strings::StrAppend(&v, "}, subdiv_perms={"); + for (const auto& p : impl_details.subdiv_permutations) { + strings::StrAppend(&v, "{"); + for (const auto& i : p) { + strings::StrAppend(&v, i, ","); + } + strings::StrAppend(&v, "}"); // one subdiv + } + strings::StrAppend(&v, "}"); // all subdivs + return v; +} + +string CollTaskParams::ToString() const { + string v = strings::StrCat("CollTaskParams {is_local={"); + for (const auto& b : is_local) { + strings::StrAppend(&v, static_cast(b), ","); + } + strings::StrAppend(&v, "}}"); + return v; +} + +string CollectiveParams::ToString() const { + string v = strings::StrCat("CollectiveParams ", name, " {", group.ToString()); + strings::StrAppend(&v, " ", instance.ToString()); + strings::StrAppend(&v, " ", task.ToString()); + strings::StrAppend(&v, " default_rank=", default_rank, + " is_source=", is_source, " subdiv_rank={"); + for (const auto& r : subdiv_rank) { + strings::StrAppend(&v, r, ","); + } + if (!subdiv_source_rank.empty()) { + strings::StrAppend(&v, " subdiv_rank={"); + for (const auto& r : subdiv_source_rank) { + strings::StrAppend(&v, r, ","); + } + strings::StrAppend(&v, "}"); + } + strings::StrAppend(&v, "}}"); + return v; +} + +/*static*/ OpKernelContext::Params* CollectiveExecutor::CtxParams( + OpKernelContext* ctx) { + return ctx->params_; +} + +/*static*/ +int64 CollectiveExecutor::kInvalidId = -1; + +} // namespace tensorflow diff --git a/tensorflow/core/framework/collective.h b/tensorflow/core/framework/collective.h new file mode 100644 index 0000000000..362d345133 --- /dev/null +++ b/tensorflow/core/framework/collective.h @@ -0,0 +1,308 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#ifndef TENSORFLOW_FRAMEWORK_COLLECTIVE_EXECUTOR_H_ +#define TENSORFLOW_FRAMEWORK_COLLECTIVE_EXECUTOR_H_ + +#include +#include + +#include "tensorflow/core/framework/device_base.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/lib/core/refcount.h" +#include "tensorflow/core/lib/core/status.h" + +namespace tensorflow { +class BufRendezvous; +class CancellationManager; +class CompleteGroupRequest; +class CompleteGroupResponse; +class CompleteInstanceRequest; +class CompleteInstanceResponse; +class DeviceLocality; +class GetStepSequenceRequest; +class GetStepSequenceResponse; +class Op; +class Tensor; + +// Types of supported collective operations. +enum CollectiveType { + REDUCTION_COLLECTIVE = 0, + BROADCAST_COLLECTIVE, + UNDEFINED_COLLECTIVE, +}; + +// Data common to all members of a device group. +// All members share the same device set but its order is +// particular to an instance so it is stored there. +struct CollGroupParams { + int32 group_key; + int32 group_size; + DeviceType device_type; + int32 num_tasks; // number of distinct tasks in group + string ToString() const; + CollGroupParams() : device_type(DEVICE_CPU) {} +}; + +// The best implementation of a collective op depends on many factors +// including the number of devices involved, the topology of +// interconnects between them and the sizes of inputs. This structure +// is used in generating and representing data movement choreography +// for each specific algorithm, hence it does not have a single, fixed +// interpretation. On first execution the runtime will update this +// structure with decisions that will guide all subsequent executions. +struct CollImplDetails { + std::vector> subdiv_permutations; + std::vector subdiv_offsets; + // broadcast only: rank of source in each subdiv + std::vector subdiv_source_rank; +}; + +// Data common to all members of a collective instance. +struct CollInstanceParams { + int32 instance_key; // Identifies all participating graph nodes. + CollectiveType type; + DataType data_type; + TensorShape shape; + // Fully qualified name of device for each member, in default rank order. + std::vector device_names; + // Task name prefix of corresponding device name. + std::vector task_names; + CollImplDetails impl_details; + string ToString() const; + CollInstanceParams& operator=(const struct CollInstanceParams& other); +}; + +// Data common to all instance members in the same task. +struct CollTaskParams { + // True for devices that are local to the process, i.e. no RPC needed. + std::vector is_local; + string ToString() const; +}; + +// Unique to a single CollectiveOp node. +struct CollectiveParams { + CollGroupParams group; + CollInstanceParams instance; + CollTaskParams task; + + string name; // node name used only for log or error messages + int default_rank; // index of this op within device_names + bool is_source; // broadcast only + // Rank of this device in each subdivision permutation. + std::vector subdiv_rank; + std::vector subdiv_source_rank; + const Tensor* in_tensor; // kernel input + Tensor* out_tensor; // kernel output + std::unique_ptr merge_op; // reduction only + std::unique_ptr final_op; // reduction only + OpKernelContext* op_context; + string ToString() const; +}; + +class CollectiveExecutor; + +// Interface that provides resolution of device localities. +class DeviceResolverInterface { + public: + virtual ~DeviceResolverInterface() {} + + // Collects DeviceLocality protobufs from all of the devices identified + // in 'col_params'. + virtual void GetDeviceLocalitiesAsync(const CollInstanceParams& inst_params, + std::vector* localities, + const StatusCallback& done) = 0; + + // Populate *locality with the DeviceLocality of the specified + // device. + virtual void GetLocalityAsync(const string& device, const string& task, + DeviceLocality* locality, + const StatusCallback& done) = 0; + + // Clear the cache of device data belonging + // to the specified task. + virtual void ClearTask(const string& task) = 0; +}; + +// Interface that provides resolution of shared CollectiveParams fields. +class ParamResolverInterface { + public: + virtual ~ParamResolverInterface() {} + + // Called by each collective op at first execution in order to fill out + // the CollectiveParams structure with data gathered from the full + // (maybe distributed) collection of peer nodes. + virtual void CompleteParamsAsync(const string& device, CollectiveParams* cp, + CancellationManager* cancel_mgr, + const StatusCallback& done) = 0; + + // Used within a distributed implementation to discover/verify + // data shared across a device group. + virtual void CompleteGroupAsync(const CompleteGroupRequest* request, + CompleteGroupResponse* response, + CancellationManager* cancel_mgr, + const StatusCallback& done) = 0; + + // Used within a distributed implementation to discover/verify data + // shared across an instance group. + virtual void CompleteInstanceAsync(const CompleteInstanceRequest* request, + CompleteInstanceResponse* response, + CancellationManager* cancel_mgr, + const StatusCallback& done) = 0; +}; + +// Graphs which utilize Collective Ops in a common instance must +// execute with identical step_ids even if they are disjoint graphs +// run by otherwise independent tasks. This interface supplies +// coordinated step_ids to use in such cases. +class StepSequenceInterface { + public: + virtual ~StepSequenceInterface() {} + + // Used with a distributed implementation to coordinate step_id + // sequences across tasks. + virtual void GetStepSequenceAsync(const GetStepSequenceRequest* request, + GetStepSequenceResponse* response, + const StatusCallback& done) = 0; + + // Refresh the local per-graph_key step_id sequence from collective + // group leader, if applicable. + virtual void RefreshStepIdSequenceAsync(int64 graph_key, + const StatusCallback& done) = 0; + + // Returns the the step_id that should be used for initiating a new execution + // on the specified graph. May return the same step_id multiple times if + // RetireStepId or RefreshStepIdReservation is not called. + virtual int64 NextStepId(int64 graph_key) = 0; + + // Reports that execution of the given step has completed successfully. + // Should be called immediately after a step completes with OK status, + // prior to calling NextStepId(). If the step fails, don't call. + virtual void RetireStepId(int64 graph_key, int64 step_id) = 0; +}; + +// Interface that provides access to per-step CollectiveExecutor +// instances and various distributed resolution capabilities. +class CollectiveExecutorMgrInterface : public StepSequenceInterface { + public: + virtual ~CollectiveExecutorMgrInterface() {} + + // Returns the step-specific CollectiveExecutor, creating if one does not + // already exist. The caller assumes ownership of one Ref on the object. + virtual CollectiveExecutor* FindOrCreate(int64 step_id) = 0; + + // If there is a CollectiveExecutor for step_id, remove it from the + // table. + virtual void Cleanup(int64 step_id) = 0; + + virtual ParamResolverInterface* GetParamResolver() const = 0; + + virtual DeviceResolverInterface* GetDeviceResolver() const = 0; +}; + +// Interface that a Collective Op implementation uses to exchange data +// with peers. Note that data exchange is currently limited to types +// for which DMAHelper::CanUseDMA() returns true, i.e. dense numeric +// types. +class PeerAccessInterface { + public: + virtual ~PeerAccessInterface() {} + + virtual void RecvFromPeer(const string& peer_device, const string& peer_task, + bool peer_is_local, const string& key, + Device* to_device, DeviceContext* to_device_ctx, + const AllocatorAttributes& to_alloc_attr, + Tensor* to_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) = 0; + + virtual void PostToPeer(const string& peer_device, const string& peer_task, + const string& key, Device* from_device, + DeviceContext* from_device_ctx, + const AllocatorAttributes& from_alloc_attr, + const Tensor* from_tensor, + const DeviceLocality& client_locality, + const StatusCallback& done) = 0; +}; + +class PerStepCollectiveRemoteAccess; + +// A step-specific object that can execute a collective operation completely +// described by a CollectiveParams object. +class CollectiveExecutor : public PeerAccessInterface, public core::RefCounted { + public: + virtual void StartAbort(const Status& s) {} + + virtual void ExecuteAsync(OpKernelContext* ctx, + const CollectiveParams& col_params, + const string& exec_key, StatusCallback done) { + done(errors::Internal( + "A collective Op has been called in a context in which " + "a CollectiveExecutor has not been provided.")); + } + + virtual void CompleteParamsAsync(const string& device, CollectiveParams* cp, + CancellationManager* cancel_mgr, + StatusCallback done) { + cem_->GetParamResolver()->CompleteParamsAsync(device, cp, cancel_mgr, done); + } + + virtual PerStepCollectiveRemoteAccess* remote_access() { return nullptr; } + + // Used to designate an invalid group or instance key. + static int64 kInvalidId; + + // Lexically scoped handle for Ref. + class Handle { + public: + explicit Handle(CollectiveExecutor* ce, bool inherit_ref) : ce_(ce) { + if (!inherit_ref) ce->Ref(); + } + ~Handle() { ce_->Unref(); } + CollectiveExecutor* get() const { return ce_; } + + private: + CollectiveExecutor* ce_; + }; + + protected: + explicit CollectiveExecutor(CollectiveExecutorMgrInterface* cem) + : cem_(cem) {} + + // For use only by derived classes + static OpKernelContext::Params* CtxParams(OpKernelContext* ctx); + CollectiveExecutorMgrInterface* cem_; + + TF_DISALLOW_COPY_AND_ASSIGN(CollectiveExecutor); +}; + +// Interface of a helper object that provices a CollectiveExecutor with +// all of the remote access it needs. +class CollectiveRemoteAccess : public PeerAccessInterface, + public DeviceResolverInterface { + public: + virtual ~CollectiveRemoteAccess() {} +}; + +// A per-step version of CollectiveRemoteAccess that cleans up outstanding +// communications in case step execution is abandoned. +class PerStepCollectiveRemoteAccess : public CollectiveRemoteAccess { + public: + virtual ~PerStepCollectiveRemoteAccess() {} + virtual void StartAbort(const Status& s) = 0; +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_FRAMEWORK_COLLECTIVE_EXECUTOR_H_ diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h index 5ccd45efc9..2d97160830 100644 --- a/tensorflow/core/framework/op_kernel.h +++ b/tensorflow/core/framework/op_kernel.h @@ -1101,6 +1101,7 @@ class OpKernelContext { void NotifyUseOfPersistentTensor(const Tensor& tensor); Status status_; + friend class CollectiveExecutor; // for access to params_ Params* params_; // not owned mutable mutex mu_; // mutable so const accessors can acquire the lock gtl::InlinedVector wrapped_allocators_ GUARDED_BY(mu_); -- GitLab From 4be2f41f30554d71ba48eb03b44d05a424bf41af Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Wed, 28 Mar 2018 17:16:10 -0700 Subject: [PATCH 383/906] Missed ScopedUnref in ResourceGather PiperOrigin-RevId: 190861558 --- tensorflow/core/kernels/resource_variable_ops.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/kernels/resource_variable_ops.cc b/tensorflow/core/kernels/resource_variable_ops.cc index e134e476f6..d1675f27dd 100644 --- a/tensorflow/core/kernels/resource_variable_ops.cc +++ b/tensorflow/core/kernels/resource_variable_ops.cc @@ -503,6 +503,7 @@ class ResourceGatherOp : public OpKernel { void Compute(OpKernelContext* c) override { Var* v = nullptr; OP_REQUIRES_OK(c, LookupResource(c, HandleFromInput(c, 0), &v)); + core::ScopedUnref su(v); // NOTE: We hold the lock for the whole gather operation instead // of increasing the reference count of v->tensor() to avoid a // situation where a write to the same variable will see a -- GitLab From 74949ee09b0ff48a2ff1ca7a27475ec6c2583d43 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 17:36:30 -0700 Subject: [PATCH 384/906] Further speed up statistical_testing_test by breaking up DKWM test. PiperOrigin-RevId: 190863893 --- .../kernel_tests/statistical_testing_test.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py b/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py index c0e7bdd259..0400c80c29 100644 --- a/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py +++ b/tensorflow/contrib/distributions/python/kernel_tests/statistical_testing_test.py @@ -141,16 +141,16 @@ class StatisticalTestingTest(test.TestCase): def test_dkwm_mean_two_sample_assertion(self): rng = np.random.RandomState(seed=0) - num_samples = 15000 + num_samples = 4000 - # 15000 samples is chosen to be enough to find discrepancies of - # size 0.1 or more with assurance 1e-6, as confirmed here: + # 4000 samples is chosen to be enough to find discrepancies of + # size 0.2 or more with assurance 1e-6, as confirmed here: with self.test_session() as sess: d = st.min_discrepancy_of_true_means_detectable_by_dkwm_two_sample( num_samples, 0., 1., num_samples, 0., 1., false_fail_rate=1e-6, false_pass_rate=1e-6) d = sess.run(d) - self.assertLess(d, 0.1) + self.assertLess(d, 0.2) # Test that the test assertion agrees that the standard # uniform distribution has the same mean as itself. @@ -160,6 +160,15 @@ class StatisticalTestingTest(test.TestCase): sess.run(st.assert_true_mean_equal_by_dkwm_two_sample( samples1, 0., 1., samples2, 0., 1., false_fail_rate=1e-6)) + def test_dkwm_mean_two_sample_assertion_beta_2_1_false(self): + rng = np.random.RandomState(seed=0) + num_samples = 4000 + samples1 = rng.uniform(size=num_samples).astype(np.float32) + + # As established above, 4000 samples is enough to find discrepancies + # of size 0.2 or more with assurance 1e-6. + + with self.test_session() as sess: # Test that the test assertion confirms that the mean of the # standard uniform distribution is different from the mean of beta(2, 1). beta_high_samples = rng.beta(2, 1, size=num_samples).astype(np.float32) @@ -169,6 +178,15 @@ class StatisticalTestingTest(test.TestCase): beta_high_samples, 0., 1., false_fail_rate=1e-6)) + def test_dkwm_mean_two_sample_assertion_beta_1_2_false(self): + rng = np.random.RandomState(seed=0) + num_samples = 4000 + samples1 = rng.uniform(size=num_samples).astype(np.float32) + + # As established above, 4000 samples is enough to find discrepancies + # of size 0.2 or more with assurance 1e-6. + + with self.test_session() as sess: # Test that the test assertion confirms that the mean of the # standard uniform distribution is different from the mean of beta(1, 2). beta_low_samples = rng.beta(1, 2, size=num_samples).astype(np.float32) -- GitLab From 628552228c76d2ee7f2eef4d56175a89941e3e1d Mon Sep 17 00:00:00 2001 From: Brennan Saeta Date: Wed, 28 Mar 2018 17:54:01 -0700 Subject: [PATCH 385/906] TPU: Implement 3rd gen input pipeline config. In this new configuration, we are able to drive a Cloud TPU at full device performance, and achieve over 3k images/sec on ResNet-50. The previous bottleneck was the un-pipeline-able split that occurred after the iterator.get_next() call. This split (when not splitting on the batch-major dimension) caused the training job to be single-threaded-CPU-bottlenecked, resulting in a performance of only ~2650 images/sec on ResNet-50. This latest input pipeline configuration requires the use of datasets. By requiring datasets, we gain the ability to call get_next() num_replicas times per host, and avoid the expensive split op. (Note: this also opens up potential future avenues for further optimization.) Despite this, we retain a lot of nice usability properties that per_host_v1 (aka input pipeline config v2) gave us. PiperOrigin-RevId: 190865741 --- .../contrib/tpu/python/tpu/tpu_config.py | 32 ++++++-- .../contrib/tpu/python/tpu/tpu_context.py | 12 ++- .../contrib/tpu/python/tpu/tpu_estimator.py | 79 +++++++++++++++++-- 3 files changed, 107 insertions(+), 16 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_config.py b/tensorflow/contrib/tpu/python/tpu/tpu_config.py index 38b5ea2310..cc1a7fd801 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_config.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_config.py @@ -35,10 +35,16 @@ _TF_CONFIG_ENV = run_config_lib._TF_CONFIG_ENV _SERVICE_KEY = run_config_lib._SERVICE_KEY _TPU_WORKER_JOB_NAME = 'tpu_worker_job_name' _NUM_CORES_PER_HOST = 8 - # pylint: enable=protected-access +class InputPipelineConfig(object): + r"""Please see the definition of these values in TPUConfig.""" + PER_SHARD_V1 = 1 + PER_HOST_V1 = 2 + PER_HOST_V2 = 3 + + # TODO(b/72511246) Provide a simplified api to configure model parallelism. class TPUConfig( collections.namedtuple('TPUConfig', [ @@ -68,13 +74,16 @@ class TPUConfig( partitioned across 4 cores which span two cores in both x and y coordinates. Please refer to @{tf.contrib.tpu.Topology} for the geometry of a TPU mesh. - per_host_input_for_training: If `True`, `input_fn` is invoked Per-Host - rather than Per-Core. With Per-Host input pipeline deployment, `input_fn` - is invoked once on each host. With Per-Core input pipeline deployment, it - is invoked once for each core. To be precise, with a global batch size - `train_batch_size` in `TPUEstimator` constructor, the batch size for each - shard is `train_batch_size` // #hosts. With Per-Core input pipeline - deployment, the shard batch size is `train_batch_size` // #cores. + per_host_input_for_training: If `True`, `PER_HOST_V1`, or `PER_HOST_V2`, + `input_fn` is invoked per-host rather than per-core. With per-host input + pipeline configuration, `input_fn` is invoked once on each host. With the + per-core input pipeline configuration, it is invoked once for each core. + With a global batch size `train_batch_size` in `TPUEstimator` constructor, + the batch size for each shard is `train_batch_size` // #hosts in the + `True` or `PER_HOST_V1` mode. In `PER_HOST_V2` mode, it is + `train_batch_size` // #cores. With the per-core input pipeline + configuration, the shard batch size is also `train_batch_size` // #cores. + Note: per_host_input_for_training==PER_SHARD_V1 only supports mode.TRAIN. tpu_job_name: The name of the TPU job. Typically, this name is auto-inferred within TPUEstimator, however when using ClusterSpec propagation in more esoteric cluster configurations, you may need to specify the job name as a @@ -117,6 +126,13 @@ class TPUConfig( raise ValueError('computation_shape elements can only be 1 or 2; got ' 'computation_shape={}'.format(computation_shape)) + # per_host_input_for_training may be True, False, or integer in [1..3]. + # Map legacy values (True, False) to numeric values. + if per_host_input_for_training is False: + per_host_input_for_training = InputPipelineConfig.PER_SHARD_V1 + elif per_host_input_for_training is True: + per_host_input_for_training = InputPipelineConfig.PER_HOST_V1 + # Check initial_infeed_sleep_secs. if initial_infeed_sleep_secs: util_lib.check_positive_integer(initial_infeed_sleep_secs, diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_context.py b/tensorflow/contrib/tpu/python/tpu/tpu_context.py index 3bac2db77e..fbc1173e49 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_context.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_context.py @@ -24,6 +24,7 @@ import copy import numpy as np from tensorflow.contrib.tpu.python.tpu import device_assignment as tpu_device_assignment +from tensorflow.contrib.tpu.python.tpu import tpu_config from tensorflow.contrib.tpu.python.tpu import tpu_system_metadata as tpu_system_metadata_lib from tensorflow.python.estimator import model_fn as model_fn_lib from tensorflow.python.platform import tf_logging as logging @@ -205,7 +206,13 @@ class _TPUContext(object): """Return true if input_fn is invoked per-core (other than per-host).""" mode = self._assert_mode() return (mode == model_fn_lib.ModeKeys.TRAIN and - not self._config.tpu_config.per_host_input_for_training) + (self._config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.PER_SHARD_V1)) + + def is_input_per_host_with_iterators(self): + """Return true if input_fn should be run in the per-host v2 config.""" + return (self._config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.PER_HOST_V2) def is_running_on_cpu(self, is_export_mode=False): """Determines whether the input_fn and model_fn should be invoked on CPU. @@ -271,7 +278,8 @@ class _TPUContext(object): return global_batch_size # On TPU - if self.is_input_sharded_per_core(): + if self.is_input_sharded_per_core() or ( + self.is_input_per_host_with_iterators()): # We prohibit per core input sharding for the model parallelism case, # therefore it is safe to use num_cores here. return global_batch_size // self.num_cores diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index 152f8c8c69..fa56708f44 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -740,6 +740,61 @@ def generate_per_host_enqueue_ops_fn_for_host( return enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset +def generate_per_host_v2_enqueue_ops_fn_for_host( + ctx, input_fn, inputs_structure_recorder, device, host_id): + """Generates infeed enqueue ops for per-host input_fn on a single host.""" + del host_id # unused + captured_infeed_queue = _CapturedObject() + hooks = [] + + with ops.device(device): + inputs = _Inputs.from_input_fn(input_fn()) + + is_dataset = inputs.is_dataset + if not is_dataset: + raise TypeError('`input_fn` must return a `Dataset` for the PER_HOST_V2 ' + 'input pipeline configuration.') + if ctx.mode == model_fn_lib.ModeKeys.PREDICT: + # TODO(b/XXX): Add predict support for PER_HOST_V2 + raise TypeError('Most PREDICT not yet supported in PER_HOST_V2 mode.') + + hooks.append(inputs.dataset_initializer_hook()) + + def enqueue_ops_fn(): + """Generates the per_host enqueue ops.""" + control_deps = [] + per_host_sharded_inputs = [] + num_replicas_per_host = ctx.num_of_replicas_per_host + with ops.device(device): + if not inputs.is_dataset: + raise TypeError('`input_fn` must return a `Dataset` for this mode.') + for _ in range(num_replicas_per_host): + # Use control dependencies to ensure a deterministic ordering. + with ops.control_dependencies(control_deps): + features, labels = inputs.features_and_labels() # Calls get_next() + + inputs_structure_recorder.validate_and_record_structure( + features, labels) + flattened_inputs = ( + inputs_structure_recorder.flatten_features_and_labels( + features, labels)) + + control_deps.extend(flattened_inputs) + per_host_sharded_inputs.append(flattened_inputs) + + infeed_queue = tpu_feed.InfeedQueue( + number_of_tuple_elements=len(per_host_sharded_inputs[0])) + captured_infeed_queue.capture(infeed_queue) + infeed_queue.set_configuration_from_sharded_input_tensors( + per_host_sharded_inputs) + + per_host_enqueue_ops = infeed_queue.generate_enqueue_ops( + per_host_sharded_inputs, tpu_ordinal_function=ctx.tpu_ordinal_function) + return per_host_enqueue_ops + + return enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset + + class _InputPipeline(object): """`_InputPipeline` handles invoking `input_fn` and piping to infeed queue. @@ -975,10 +1030,17 @@ class _InputPipeline(object): host_device = tpu_host_placement_fn(host_id=host_id) with ops.device(host_device): with ops.name_scope('input_pipeline_task%d' % (host_id)): - enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset = ( - generate_per_host_enqueue_ops_fn_for_host( - self._ctx, self._input_fn, self._inputs_structure_recorder, - self._batch_axis, host_device, host_id)) + if self._ctx.is_input_per_host_with_iterators(): + enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset = ( + generate_per_host_v2_enqueue_ops_fn_for_host( + self._ctx, self._input_fn, + self._inputs_structure_recorder, host_device, host_id)) + else: + enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset = ( + generate_per_host_enqueue_ops_fn_for_host( + self._ctx, self._input_fn, + self._inputs_structure_recorder, self._batch_axis, + host_device, host_id)) all_hooks.extend(hooks) # NOTE(xiejw): We dispatch here based on the return type of the @@ -1724,7 +1786,7 @@ class TPUEstimator(estimator_lib.Estimator): labels to match up with the corresponding images. If None is supplied, and per_host_input_for_training is True, batches will be sharded based on the major dimension. If tpu_config.per_host_input_for_training is - False, batch_axis is ignored. + False or `PER_HOST_V2`, batch_axis is ignored. Raises: ValueError: `params` has reserved keys already. @@ -1744,7 +1806,8 @@ class TPUEstimator(estimator_lib.Estimator): raise ValueError('`train_batch_size` cannot be `None`') util_lib.check_positive_integer(train_batch_size, 'train_batch_size') - if (not config.tpu_config.per_host_input_for_training and + if (config.tpu_config.per_host_input_for_training is + tpu_config.InputPipelineConfig.PER_SHARD_V1 and config.tpu_config.computation_shape): raise ValueError( 'Model parallelism only supports per host input for training. ' @@ -2362,6 +2425,10 @@ class _Inputs(object): def features_and_labels(self): """Gets `features` and `labels`.""" if self.is_dataset: + if self._iterator is None: + raise RuntimeError('Internal error: Must call dataset_initializer_hook ' + 'before calling features_and_labels(). Please file ' + 'a bug!') return _Inputs._parse_inputs(self._iterator.get_next()) return (self._features, self._labels) -- GitLab From 17dfe3ed7db7fb4d41f8933adead4737c30a92c9 Mon Sep 17 00:00:00 2001 From: Igor Ganichev Date: Wed, 28 Mar 2018 18:26:30 -0700 Subject: [PATCH 386/906] Implement assert_same_structure in C++ Also implements helper functions nest._is_namedtuple nest._same_namedtuple. Also, fix a bug in FlattenHelper where error from recursive calls were not propagated up immediately. This change implements a good chunk of machinery that will allow us to move map_structure to C++. Before: entry { name: "NestBenchmark.assert_same_structure_6_elem" iters: 30000 wall_time: 4.79532718658e-05 } entry { name: "NestBenchmark.assert_same_structure_60_elem" iters: 30000 wall_time: 0.000403008667628 } After: entry { name: "NestBenchmark.assert_same_structure_6_elem" iters: 30000 wall_time: 1.65301720301e-05 } entry { name: "NestBenchmark.assert_same_structure_60_elem" iters: 30000 wall_time: 0.000147621099154 } PiperOrigin-RevId: 190869007 --- tensorflow/python/BUILD | 1 + tensorflow/python/framework/test_util.py | 8 +- .../kernel_tests/functional_ops_test.py | 4 +- tensorflow/python/util/nest.py | 90 +---- tensorflow/python/util/nest_test.py | 156 +++++--- tensorflow/python/util/util.cc | 374 +++++++++++++++++- tensorflow/python/util/util.h | 51 +++ tensorflow/python/util/util.i | 9 + 8 files changed, 545 insertions(+), 148 deletions(-) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 4f61c01f65..09c1965d7e 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -298,6 +298,7 @@ cc_library( srcs = ["util/util.cc"], hdrs = ["util/util.h"], deps = [ + ":safe_ptr", "//tensorflow/core:framework", "//tensorflow/core:lib", "//util/python:python_headers", diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py index 4192a27f65..bf00fa6439 100644 --- a/tensorflow/python/framework/test_util.py +++ b/tensorflow/python/framework/test_util.py @@ -487,7 +487,13 @@ def assert_no_new_pyobjects_executing_eagerly(f): gc.collect() # There should be no new Python objects hanging around. new_count = len(gc.get_objects()) - self.assertEqual(previous_count, new_count) + # In some cases (specifacally on MacOS), new_count is somehow + # smaller than previous_count. + # Using plain assert because not all classes using this decorator + # have assertLessEqual + assert new_count <= previous_count, ( + "new_count(%d) is not less than or equal to previous_count(%d)" % ( + new_count, previous_count)) gc.enable() return decorator diff --git a/tensorflow/python/kernel_tests/functional_ops_test.py b/tensorflow/python/kernel_tests/functional_ops_test.py index f5717a5a21..1301ef9d19 100644 --- a/tensorflow/python/kernel_tests/functional_ops_test.py +++ b/tensorflow/python/kernel_tests/functional_ops_test.py @@ -229,7 +229,7 @@ class FunctionalOpsTest(test.TestCase): with self.test_session(): nums = np.array([1, 2, 3, 4, 5, 6]) with self.assertRaisesRegexp( - TypeError, r"two structures don't have the same sequence type."): + TypeError, r"two structures don't have the same nested structure"): # lambda emits tuple, but dtype is a list functional_ops.map_fn( lambda x: ((x + 3) * 2, -(x + 3) * 2), @@ -316,7 +316,7 @@ class FunctionalOpsTest(test.TestCase): initializer = np.array(1.0) # Multiply a * 1 each time with self.assertRaisesRegexp( - ValueError, "two structures don't have the same number of elements"): + ValueError, "two structures don't have the same nested structure"): functional_ops.scan(lambda a, x: (a, -a), elems, initializer) def testScan_Scoped(self): diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py index 23c2c48f4b..5622431bc9 100644 --- a/tensorflow/python/util/nest.py +++ b/tensorflow/python/util/nest.py @@ -60,15 +60,7 @@ def _is_namedtuple(instance, strict=False): Returns: True if `instance` is a `namedtuple`. """ - # Attemp to limit the test to plain namedtuple (not stuff inheriting from it). - if not isinstance(instance, tuple): - return False - if strict and instance.__class__.__base__ != tuple: - return False - return ( - hasattr(instance, "_fields") and - isinstance(instance._fields, _collections.Sequence) and - all(isinstance(f, _six.string_types) for f in instance._fields)) + return _pywrap_tensorflow.IsNamedtuple(instance, strict) def _sequence_like(instance, args): @@ -157,76 +149,7 @@ def flatten(nest): def _same_namedtuples(nest1, nest2): """Returns True if the two namedtuples have the same name and fields.""" - if nest1._fields != nest2._fields: - return False - if nest1.__class__.__name__ != nest2.__class__.__name__: - return False - return True - - -def _recursive_assert_same_structure(nest1, nest2, check_types): - """Helper function for `assert_same_structure`. - - See `assert_same_structure` for further information about namedtuples. - - Args: - nest1: An arbitrarily nested structure. - nest2: An arbitrarily nested structure. - check_types: If `True` (default) types of sequences are checked as - well, including the keys of dictionaries. If set to `False`, for example - a list and a tuple of objects will look the same if they have the same - size. Note that namedtuples with identical name and fields are always - considered to have the same shallow structure. - - Returns: - True if `nest1` and `nest2` have the same structure. - - Raises: - ValueError: If the two structure don't have the same nested structre. - TypeError: If the two structure don't have the same sequence type. - ValueError: If the two dictionaries don't have the same set of keys. - """ - is_sequence_nest1 = is_sequence(nest1) - if is_sequence_nest1 != is_sequence(nest2): - raise ValueError( - "The two structures don't have the same nested structure.\n\n" - "First structure: %s\n\nSecond structure: %s." % (nest1, nest2)) - - if not is_sequence_nest1: - return # finished checking - - if check_types: - type_nest1 = type(nest1) - type_nest2 = type(nest2) - - # Duck-typing means that nest should be fine with two different namedtuples - # with identical name and fields. - if _is_namedtuple(nest1, True) and _is_namedtuple(nest2, True): - if not _same_namedtuples(nest1, nest2): - raise TypeError( - "The two namedtuples don't have the same sequence type. First " - "structure has type %s, while second structure has type %s." - % (type_nest1, type_nest2)) - else: - if type_nest1 != type_nest2: - raise TypeError( - "The two structures don't have the same sequence type. First " - "structure has type %s, while second structure has type %s." - % (type_nest1, type_nest2)) - - if isinstance(nest1, dict): - keys1 = set(_six.iterkeys(nest1)) - keys2 = set(_six.iterkeys(nest2)) - if keys1 != keys2: - raise ValueError( - "The two dictionaries don't have the same set of keys. First " - "structure has keys {}, while second structure has keys {}." - .format(keys1, keys2)) - - nest1_as_sequence = [n for n in _yield_value(nest1)] - nest2_as_sequence = [n for n in _yield_value(nest2)] - for n1, n2 in zip(nest1_as_sequence, nest2_as_sequence): - _recursive_assert_same_structure(n1, n2, check_types) + return _pywrap_tensorflow.SameNamedtuples(nest1, nest2) def assert_same_structure(nest1, nest2, check_types=True): @@ -257,14 +180,7 @@ def assert_same_structure(nest1, nest2, check_types=True): TypeError: If the two structures differ in the type of sequence in any of their substructures. Only possible if `check_types` is `True`. """ - len_nest1 = len(flatten(nest1)) if is_sequence(nest1) else 1 - len_nest2 = len(flatten(nest2)) if is_sequence(nest2) else 1 - if len_nest1 != len_nest2: - raise ValueError("The two structures don't have the same number of " - "elements.\n\nFirst structure (%i elements): %s\n\n" - "Second structure (%i elements): %s" - % (len_nest1, nest1, len_nest2, nest2)) - _recursive_assert_same_structure(nest1, nest2, check_types) + _pywrap_tensorflow.AssertSameStructure(nest1, nest2, check_types) def flatten_dict_items(dictionary): diff --git a/tensorflow/python/util/nest_test.py b/tensorflow/python/util/nest_test.py index 4439d6241e..2f12b25354 100644 --- a/tensorflow/python/util/nest_test.py +++ b/tensorflow/python/util/nest_test.py @@ -19,11 +19,14 @@ from __future__ import division from __future__ import print_function import collections +import time import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import test_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.platform import test @@ -32,6 +35,9 @@ from tensorflow.python.util import nest class NestTest(test.TestCase): + PointXY = collections.namedtuple("Point", ["x", "y"]) # pylint: disable=invalid-name + + @test_util.assert_no_new_pyobjects_executing_eagerly def testFlattenAndPack(self): structure = ((3, 4), 5, (6, 7, (9, 10), 8)) flat = ["a", "b", "c", "d", "e", "f", "g", "h"] @@ -39,8 +45,8 @@ class NestTest(test.TestCase): self.assertEqual( nest.pack_sequence_as(structure, flat), (("a", "b"), "c", ("d", "e", ("f", "g"), "h"))) - point = collections.namedtuple("Point", ["x", "y"]) - structure = (point(x=4, y=2), ((point(x=1, y=0),),)) + structure = (NestTest.PointXY(x=4, y=2), + ((NestTest.PointXY(x=1, y=0),),)) flat = [4, 2, 1, 0] self.assertEqual(nest.flatten(structure), flat) restructured_from_flat = nest.pack_sequence_as(structure, flat) @@ -66,6 +72,7 @@ class NestTest(test.TestCase): with self.assertRaises(ValueError): nest.pack_sequence_as([5, 6, [7, 8]], ["a", "b", "c"]) + @test_util.assert_no_new_pyobjects_executing_eagerly def testFlattenDictOrder(self): """`flatten` orders dicts by key, including OrderedDicts.""" ordered = collections.OrderedDict([("d", 3), ("b", 1), ("a", 0), ("c", 2)]) @@ -87,12 +94,14 @@ class NestTest(test.TestCase): ordered_reconstruction) self.assertEqual({"d": 3, "b": 1, "a": 0, "c": 2}, plain_reconstruction) + Abc = collections.namedtuple("A", ("b", "c")) # pylint: disable=invalid-name + + @test_util.assert_no_new_pyobjects_executing_eagerly def testFlattenAndPack_withDicts(self): # A nice messy mix of tuples, lists, dicts, and `OrderedDict`s. - named_tuple = collections.namedtuple("A", ("b", "c")) mess = [ "z", - named_tuple(3, 4), + NestTest.Abc(3, 4), { "c": [ 1, @@ -111,7 +120,7 @@ class NestTest(test.TestCase): structure_of_mess = [ 14, - named_tuple("a", True), + NestTest.Abc("a", True), { "c": [ 0, @@ -157,6 +166,7 @@ class NestTest(test.TestCase): nest.pack_sequence_as(["hello", "world"], ["and", "goodbye", "again"]) + @test_util.assert_no_new_pyobjects_executing_eagerly def testIsSequence(self): self.assertFalse(nest.is_sequence("1234")) self.assertTrue(nest.is_sequence([1, 3, [4, 5]])) @@ -186,6 +196,23 @@ class NestTest(test.TestCase): ValueError, "Key had [0-9]* elements, but value had [0-9]* elements"): nest.flatten_dict_items(another_bad_dictionary) + # pylint does not correctly recognize these as class names and + # suggests to use variable style under_score naming. + # pylint: disable=invalid-name + Named0ab = collections.namedtuple("named_0", ("a", "b")) + Named1ab = collections.namedtuple("named_1", ("a", "b")) + SameNameab = collections.namedtuple("same_name", ("a", "b")) + SameNameab2 = collections.namedtuple("same_name", ("a", "b")) + SameNamexy = collections.namedtuple("same_name", ("x", "y")) + SameName1xy = collections.namedtuple("same_name_1", ("x", "y")) + SameName1xy2 = collections.namedtuple("same_name_1", ("x", "y")) + NotSameName = collections.namedtuple("not_same_name", ("a", "b")) + # pylint: enable=invalid-name + + class SameNamedType1(SameNameab): + pass + + @test_util.assert_no_new_pyobjects_executing_eagerly def testAssertSameStructure(self): structure1 = (((1, 2), 3), 4, (5, 6)) structure2 = ((("foo1", "foo2"), "foo3"), "foo4", ("foo5", "foo6")) @@ -198,23 +225,32 @@ class NestTest(test.TestCase): with self.assertRaisesRegexp( ValueError, - ("don't have the same number of elements\\.\n\n" - "First structure \\(6 elements\\):.*?" - "\n\nSecond structure \\(2 elements\\):")): + ("The two structures don't have the same nested structure\\.\n\n" + "First structure:.*?\n\n" + "Second structure:.*\n\n" + "More specifically: Substructure " + r'"type=tuple str=\(\(1, 2\), 3\)" is a sequence, while ' + 'substructure "type=str str=spam" is not')): nest.assert_same_structure(structure1, structure_different_num_elements) with self.assertRaisesRegexp( ValueError, - ("don't have the same number of elements\\.\n\n" - "First structure \\(2 elements\\):.*?" - "\n\nSecond structure \\(1 elements\\):")): + ("The two structures don't have the same nested structure\\.\n\n" + "First structure:.*?\n\n" + "Second structure:.*\n\n" + r'More specifically: Substructure "type=list str=\[0, 1\]" ' + r'is a sequence, while substructure "type=ndarray str=\[0 1\]" ' + "is not")): nest.assert_same_structure([0, 1], np.array([0, 1])) with self.assertRaisesRegexp( ValueError, - ("don't have the same number of elements\\.\n\n" - "First structure \\(1 elements\\):.*" - "\n\nSecond structure \\(2 elements\\):")): + ("The two structures don't have the same nested structure\\.\n\n" + "First structure:.*?\n\n" + "Second structure:.*\n\n" + r'More specifically: Substructure "type=list str=\[0, 1\]" ' + 'is a sequence, while substructure "type=int str=0" ' + "is not")): nest.assert_same_structure(0, [0, 1]) self.assertRaises(TypeError, nest.assert_same_structure, (0, 1), [0, 1]) @@ -225,21 +261,21 @@ class NestTest(test.TestCase): "First structure: .*?\n\nSecond structure: ")): nest.assert_same_structure(structure1, structure_different_nesting) - named_type_0 = collections.namedtuple("named_0", ("a", "b")) - named_type_1 = collections.namedtuple("named_1", ("a", "b")) self.assertRaises(TypeError, nest.assert_same_structure, (0, 1), - named_type_0("a", "b")) + NestTest.Named0ab("a", "b")) - nest.assert_same_structure(named_type_0(3, 4), named_type_0("a", "b")) + nest.assert_same_structure(NestTest.Named0ab(3, 4), + NestTest.Named0ab("a", "b")) self.assertRaises(TypeError, nest.assert_same_structure, - named_type_0(3, 4), named_type_1(3, 4)) + NestTest.Named0ab(3, 4), NestTest.Named1ab(3, 4)) with self.assertRaisesRegexp( ValueError, ("don't have the same nested structure\\.\n\n" "First structure: .*?\n\nSecond structure: ")): - nest.assert_same_structure(named_type_0(3, 4), named_type_0([3], 4)) + nest.assert_same_structure(NestTest.Named0ab(3, 4), + NestTest.Named0ab([3], 4)) with self.assertRaisesRegexp( ValueError, @@ -258,36 +294,33 @@ class NestTest(test.TestCase): "don't have the same set of keys"): nest.assert_same_structure({"a": 1}, {"b": 1}) - same_name_type_0 = collections.namedtuple("same_name", ("a", "b")) - same_name_type_1 = collections.namedtuple("same_name", ("a", "b")) - nest.assert_same_structure(same_name_type_0(0, 1), same_name_type_1(2, 3)) + nest.assert_same_structure(NestTest.SameNameab(0, 1), + NestTest.SameNameab2(2, 3)) # This assertion is expected to pass: two namedtuples with the same # name and field names are considered to be identical. - same_name_type_2 = collections.namedtuple("same_name_1", ("x", "y")) - same_name_type_3 = collections.namedtuple("same_name_1", ("x", "y")) nest.assert_same_structure( - same_name_type_0(same_name_type_2(0, 1), 2), - same_name_type_1(same_name_type_3(2, 3), 4)) + NestTest.SameNameab(NestTest.SameName1xy(0, 1), 2), + NestTest.SameNameab2(NestTest.SameName1xy2(2, 3), 4)) expected_message = "The two structures don't have the same.*" with self.assertRaisesRegexp(ValueError, expected_message): - nest.assert_same_structure(same_name_type_0(0, same_name_type_1(1, 2)), - same_name_type_1(same_name_type_0(0, 1), 2)) + nest.assert_same_structure( + NestTest.SameNameab(0, NestTest.SameNameab2(1, 2)), + NestTest.SameNameab2(NestTest.SameNameab(0, 1), 2)) - same_name_type_1 = collections.namedtuple("not_same_name", ("a", "b")) self.assertRaises(TypeError, nest.assert_same_structure, - same_name_type_0(0, 1), same_name_type_1(2, 3)) + NestTest.SameNameab(0, 1), NestTest.NotSameName(2, 3)) - same_name_type_1 = collections.namedtuple("same_name", ("x", "y")) self.assertRaises(TypeError, nest.assert_same_structure, - same_name_type_0(0, 1), same_name_type_1(2, 3)) + NestTest.SameNameab(0, 1), NestTest.SameNamexy(2, 3)) - class SameNamedType1(collections.namedtuple("same_name", ("a", "b"))): - pass self.assertRaises(TypeError, nest.assert_same_structure, - same_name_type_0(0, 1), SameNamedType1(2, 3)) + NestTest.SameNameab(0, 1), NestTest.SameNamedType1(2, 3)) + EmptyNT = collections.namedtuple("empty_nt", "") # pylint: disable=invalid-name + + @test_util.assert_no_new_pyobjects_executing_eagerly def testMapStructure(self): structure1 = (((1, 2), 3), 4, (5, 6)) structure2 = (((7, 8), 9), 10, (11, 12)) @@ -310,9 +343,8 @@ class NestTest(test.TestCase): self.assertEqual((), nest.map_structure(lambda x: x + 1, ())) self.assertEqual([], nest.map_structure(lambda x: x + 1, [])) self.assertEqual({}, nest.map_structure(lambda x: x + 1, {})) - empty_nt = collections.namedtuple("empty_nt", "") - self.assertEqual(empty_nt(), nest.map_structure(lambda x: x + 1, - empty_nt())) + self.assertEqual(NestTest.EmptyNT(), nest.map_structure(lambda x: x + 1, + NestTest.EmptyNT())) # This is checking actual equality of types, empty list != empty tuple self.assertNotEqual((), nest.map_structure(lambda x: x + 1, [])) @@ -352,10 +384,12 @@ class NestTest(test.TestCase): with self.assertRaisesRegexp(ValueError, "Only valid keyword argument"): nest.map_structure(lambda x: None, structure1, check_types=False, foo="a") + ABTuple = collections.namedtuple("ab_tuple", "a, b") # pylint: disable=invalid-name + + @test_util.assert_no_new_pyobjects_executing_eagerly def testMapStructureWithStrings(self): - ab_tuple = collections.namedtuple("ab_tuple", "a, b") - inp_a = ab_tuple(a="foo", b=("bar", "baz")) - inp_b = ab_tuple(a=2, b=(1, 3)) + inp_a = NestTest.ABTuple(a="foo", b=("bar", "baz")) + inp_b = NestTest.ABTuple(a=2, b=(1, 3)) out = nest.map_structure(lambda string, repeats: string * repeats, inp_a, inp_b) @@ -363,8 +397,8 @@ class NestTest(test.TestCase): self.assertEqual("bar", out.b[0]) self.assertEqual("bazbazbaz", out.b[1]) - nt = ab_tuple(a=("something", "something_else"), - b="yet another thing") + nt = NestTest.ABTuple(a=("something", "something_else"), + b="yet another thing") rev_nt = nest.map_structure(lambda x: x[::-1], nt) # Check the output is the correct structure, and all strings are reversed. nest.assert_same_structure(nt, rev_nt) @@ -431,10 +465,8 @@ class NestTest(test.TestCase): # This assertion is expected to pass: two namedtuples with the same # name and field names are considered to be identical. - same_name_type_0 = collections.namedtuple("same_name", ("a", "b")) - same_name_type_1 = collections.namedtuple("same_name", ("a", "b")) - inp_shallow = same_name_type_0(1, 2) - inp_deep = same_name_type_1(1, [1, 2, 3]) + inp_shallow = NestTest.SameNameab(1, 2) + inp_deep = NestTest.SameNameab2(1, [1, 2, 3]) nest.assert_shallow_structure(inp_shallow, inp_deep, check_types=False) nest.assert_shallow_structure(inp_shallow, inp_deep, check_types=True) @@ -466,7 +498,7 @@ class NestTest(test.TestCase): [1, {"c": 2}, 3, (4, 5)]) # Namedtuples. - ab_tuple = collections.namedtuple("ab_tuple", "a, b") + ab_tuple = NestTest.ABTuple input_tree = ab_tuple(a=[0, 1], b=2) shallow_tree = ab_tuple(a=0, b=1) input_tree_flattened_as_shallow_tree = nest.flatten_up_to(shallow_tree, @@ -681,5 +713,31 @@ class NestTest(test.TestCase): list(nest.flatten_with_joined_string_paths(inputs)), expected) +class NestBenchmark(test.Benchmark): + + def run_and_report(self, s1, s2, name): + burn_iter, test_iter = 100, 30000 + + for _ in xrange(burn_iter): + nest.assert_same_structure(s1, s2) + + t0 = time.time() + for _ in xrange(test_iter): + nest.assert_same_structure(s1, s2) + t1 = time.time() + + self.report_benchmark(iters=test_iter, wall_time=(t1 - t0) / test_iter, + name=name) + + def benchmark_assert_structure(self): + s1 = (((1, 2), 3), 4, (5, 6)) + s2 = ((("foo1", "foo2"), "foo3"), "foo4", ("foo5", "foo6")) + self.run_and_report(s1, s2, "assert_same_structure_6_elem") + + s1 = (((1, 2), 3), 4, (5, 6)) * 10 + s2 = ((("foo1", "foo2"), "foo3"), "foo4", ("foo5", "foo6")) * 10 + self.run_and_report(s1, s2, "assert_same_structure_60_elem") + + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/util/util.cc b/tensorflow/python/util/util.cc index a41fa7df25..70aee4a3f6 100644 --- a/tensorflow/python/util/util.cc +++ b/tensorflow/python/util/util.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/logging.h" +#include "tensorflow/python/lib/core/safe_ptr.h" namespace tensorflow { namespace swig { @@ -27,6 +28,113 @@ PyObject* CollectionsSequenceType = nullptr; bool WarnedThatSetIsNotSequence = false; +bool IsString(PyObject* o) { + return PyBytes_Check(o) || +#if PY_MAJOR_VERSION < 3 + PyString_Check(o) || +#endif + PyUnicode_Check(o); +} + +// Equivalent to Python's 'o.__class__.__name__' +// Note that '__class__' attribute is set only in new-style classes. +// A lot of tensorflow code uses __class__ without checks, so it seems like +// we only support new-style classes. +StringPiece GetClassName(PyObject* o) { + // __class__ is equivalent to type() for new style classes. + // type() is equivalent to PyObject_Type() + // (https://docs.python.org/3.5/c-api/object.html#c.PyObject_Type) + // PyObject_Type() is equivalent to o->ob_type except for Py_INCREF, which + // we don't need here. + PyTypeObject* type = o->ob_type; + + // __name__ is the value of `tp_name` after the last '.' + // (https://docs.python.org/2/c-api/typeobj.html#c.PyTypeObject.tp_name) + StringPiece name(type->tp_name); + size_t pos = name.rfind('.'); + if (pos != StringPiece::npos) { + name.remove_prefix(pos + 1); + } + return name; +} + +string PyObjectToString(PyObject* o) { + if (o == nullptr) { + return ""; + } + PyObject* str = PyObject_Str(o); + if (str) { +#if PY_MAJOR_VERSION < 3 + string s(PyString_AS_STRING(str)); +#else + string s(PyUnicode_AsUTF8(str)); +#endif + Py_DECREF(str); + return tensorflow::strings::StrCat("type=", GetClassName(o), " str=", s); + } else { + return ""; + } +} + +// Implements the same idea as tensorflow.util.nest._yield_value +// During construction we check if the iterable is a dictionary. +// If so, we construct a sequence from its sorted keys that will be used +// for iteration. +// If not, we construct a sequence directly from the iterable. +// At each step, we get the next element from the sequence and use it +// either as a key or return it directly. +// +// 'iterable' must not be modified while ValIterator is used. +class ValIterator { + public: + explicit ValIterator(PyObject* iterable) : dict_(nullptr), index_(0) { + if (PyDict_Check(iterable)) { + dict_ = iterable; + // PyDict_Keys returns a list, which can be used with + // PySequence_Fast_GET_ITEM. + seq_ = PyDict_Keys(iterable); + // Iterate through dictionaries in a deterministic order by sorting the + // keys. Notice this means that we ignore the original order of + // `OrderedDict` instances. This is intentional, to avoid potential + // bugs caused by mixing ordered and plain dicts (e.g., flattening + // a dict but using a corresponding `OrderedDict` to pack it back). + PyList_Sort(seq_); + } else { + seq_ = PySequence_Fast(iterable, ""); + } + size_ = PySequence_Fast_GET_SIZE(seq_); + } + + ~ValIterator() { Py_DECREF(seq_); } + + // Return a borrowed reference to the next element from iterable. + // Return nullptr when iteration is over. + PyObject* next() { + PyObject* element = nullptr; + if (index_ < size_) { + // Both PySequence_Fast_GET_ITEM and PyDict_GetItem return borrowed + // references. + element = PySequence_Fast_GET_ITEM(seq_, index_); + ++index_; + if (dict_ != nullptr) { + element = PyDict_GetItem(dict_, element); + if (element == nullptr) { + PyErr_SetString(PyExc_RuntimeError, + "Dictionary was modified during iteration over it"); + return nullptr; + } + } + } + return element; + } + + private: + PyObject* seq_; + PyObject* dict_; + Py_ssize_t size_; + Py_ssize_t index_; +}; + // Returns 1 if `o` is considered a sequence for the purposes of Flatten(). // Returns 0 otherwise. // Returns -1 if an error occurred. @@ -38,7 +146,7 @@ int IsSequenceHelper(PyObject* o) { "so consider avoiding using them."; WarnedThatSetIsNotSequence = true; } - if (CollectionsSequenceType == nullptr) { + if (TF_PREDICT_FALSE(CollectionsSequenceType == nullptr)) { PyErr_SetString( PyExc_RuntimeError, tensorflow::strings::StrCat( @@ -49,11 +157,7 @@ int IsSequenceHelper(PyObject* o) { } int is_instance = PyObject_IsInstance(o, CollectionsSequenceType); if (is_instance == -1) return -1; - return static_cast(is_instance != 0 && !PyBytes_Check(o) && -#if PY_MAJOR_VERSION < 3 - !PyString_Check(o) && -#endif - !PyUnicode_Check(o)); + return static_cast(is_instance != 0 && !IsString(o)); } bool FlattenHelper(PyObject* nested, PyObject* list) { @@ -75,12 +179,16 @@ bool FlattenHelper(PyObject* nested, PyObject* list) { // while the method is running. PyObject* key = PyList_GET_ITEM(keys, i); PyObject* val = PyDict_GetItem(nested, key); - if (Py_EnterRecursiveCall(" in Flatten")) { + if (Py_EnterRecursiveCall(" in flatten")) { Py_DECREF(keys); return false; } - FlattenHelper(val, list); + const bool success = FlattenHelper(val, list); Py_LeaveRecursiveCall(); + if (!success) { + Py_DECREF(keys); + return false; + } } Py_DECREF(keys); return true; @@ -90,13 +198,159 @@ bool FlattenHelper(PyObject* nested, PyObject* list) { PyObject* item; PyObject* iterator = PyObject_GetIter(nested); while ((item = PyIter_Next(iterator)) != nullptr) { - FlattenHelper(item, list); + if (Py_EnterRecursiveCall(" in flatten")) { + Py_DECREF(iterator); + Py_DECREF(item); + return false; + } + bool success = FlattenHelper(item, list); + Py_LeaveRecursiveCall(); + if (!success) { + Py_DECREF(iterator); + Py_DECREF(item); + return false; + } Py_DECREF(item); } Py_DECREF(iterator); return true; } +// Sets error using keys of 'dict1' and 'dict2'. +// 'dict1' and 'dict2' are assumed to be Python dictionaries. +void SetDifferentKeysError(PyObject* dict1, PyObject* dict2, string* error_msg, + bool* is_type_error) { + PyObject* k1 = PyDict_Keys(dict1); + PyObject* k2 = PyDict_Keys(dict2); + *is_type_error = false; + *error_msg = tensorflow::strings::StrCat( + "The two dictionaries don't have the same set of keys. " + "First structure has keys ", + PyObjectToString(k1), ", while second structure has keys ", + PyObjectToString(k2)); + Py_DECREF(k1); + Py_DECREF(k2); +} + +// Returns true iff there were no "internal" errors. In other words, +// errors that has nothing to do with structure checking. +// If an "internal" error occured, the appropriate Python error will be +// set and the caller can propage it directly to the user. +// +// Both `error_msg` and `is_type_error` must be non-null. `error_msg` must +// be empty. +// Leaves `error_msg` empty if structures matched. Else, fills `error_msg` +// with appropriate error and sets `is_type_error` to true iff +// the error to be raised should be TypeError. +bool AssertSameStructureHelper(PyObject* o1, PyObject* o2, bool check_types, + string* error_msg, bool* is_type_error) { + DCHECK(error_msg); + DCHECK(is_type_error); + const bool is_seq1 = IsSequence(o1); + const bool is_seq2 = IsSequence(o2); + if (PyErr_Occurred()) return false; + if (is_seq1 != is_seq2) { + string seq_str = is_seq1 ? PyObjectToString(o1) : PyObjectToString(o2); + string non_seq_str = is_seq1 ? PyObjectToString(o2) : PyObjectToString(o1); + *is_type_error = false; + *error_msg = tensorflow::strings::StrCat( + "Substructure \"", seq_str, "\" is a sequence, while substructure \"", + non_seq_str, "\" is not"); + return true; + } + + // Got to scalars, so finished checking. Structures are the same. + if (!is_seq1) return true; + + if (check_types) { + const PyTypeObject* type1 = o1->ob_type; + const PyTypeObject* type2 = o2->ob_type; + + // We treat two different namedtuples with identical name and fields + // as having the same type. + const PyObject* o1_tuple = IsNamedtuple(o1, true); + if (o1_tuple == nullptr) return false; + const PyObject* o2_tuple = IsNamedtuple(o2, true); + if (o2_tuple == nullptr) { + Py_DECREF(o1_tuple); + return false; + } + bool both_tuples = o1_tuple == Py_True && o2_tuple == Py_True; + Py_DECREF(o1_tuple); + Py_DECREF(o2_tuple); + + if (both_tuples) { + const PyObject* same_tuples = SameNamedtuples(o1, o2); + if (same_tuples == nullptr) return false; + bool not_same_tuples = same_tuples != Py_True; + Py_DECREF(same_tuples); + if (not_same_tuples) { + *is_type_error = true; + *error_msg = tensorflow::strings::StrCat( + "The two namedtuples don't have the same sequence type. " + "First structure ", + PyObjectToString(o1), " has type ", type1->tp_name, + ", while second structure ", PyObjectToString(o2), " has type ", + type2->tp_name); + return true; + } + } else if (type1 != type2) { + *is_type_error = true; + *error_msg = tensorflow::strings::StrCat( + "The two namedtuples don't have the same sequence type. " + "First structure ", + PyObjectToString(o1), " has type ", type1->tp_name, + ", while second structure ", PyObjectToString(o2), " has type ", + type2->tp_name); + return true; + } + + if (PyDict_Check(o1)) { + if (PyDict_Size(o1) != PyDict_Size(o2)) { + SetDifferentKeysError(o1, o2, error_msg, is_type_error); + return true; + } + + PyObject* key; + Py_ssize_t pos = 0; + while (PyDict_Next(o1, &pos, &key, nullptr)) { + if (PyDict_GetItem(o2, key) == nullptr) { + SetDifferentKeysError(o1, o2, error_msg, is_type_error); + return true; + } + } + } + } + + ValIterator iter1(o1); + ValIterator iter2(o2); + + while (true) { + PyObject* v1 = iter1.next(); + PyObject* v2 = iter2.next(); + if (v1 != nullptr && v2 != nullptr) { + if (Py_EnterRecursiveCall(" in assert_same_structure")) { + return false; + } + bool no_internal_errors = AssertSameStructureHelper( + v1, v2, check_types, error_msg, is_type_error); + Py_LeaveRecursiveCall(); + if (!no_internal_errors) return false; + if (!error_msg->empty()) return true; + } else if (v1 == nullptr && v2 == nullptr) { + // Done with all recursive calls. Structure matched. + return true; + } else { + *is_type_error = false; + *error_msg = tensorflow::strings::StrCat( + "The two structures don't have the same number of elements. ", + "First structure: ", PyObjectToString(o1), + ". Second structure: ", PyObjectToString(o2)); + return true; + } + } +} + } // anonymous namespace void RegisterSequenceClass(PyObject* sequence_class) { @@ -123,5 +377,107 @@ PyObject* Flatten(PyObject* nested) { return nullptr; } } + +PyObject* IsNamedtuple(PyObject* o, bool strict) { + // Must be subclass of tuple + if (!PyTuple_Check(o)) { + Py_RETURN_FALSE; + } + + // If strict, o.__class__.__base__ must be tuple + if (strict) { + PyObject* klass = PyObject_GetAttrString(o, "__class__"); + if (klass == nullptr) return nullptr; + PyObject* base = PyObject_GetAttrString(klass, "__base__"); + Py_DECREF(klass); + if (base == nullptr) return nullptr; + + const PyTypeObject* base_type = reinterpret_cast(base); + // built-in object types are singletons + bool tuple_base = base_type == &PyTuple_Type; + Py_DECREF(base); + if (!tuple_base) { + Py_RETURN_FALSE; + } + } + + if (TF_PREDICT_FALSE(CollectionsSequenceType == nullptr)) { + PyErr_SetString( + PyExc_RuntimeError, + tensorflow::strings::StrCat( + "collections.Sequence type has not been set. " + "Please call RegisterSequenceClass before using this module") + .c_str()); + return nullptr; + } + + // o must have attribute '_fields' and every element in + // '_fields' must be a string. + int has_fields = PyObject_HasAttrString(o, "_fields"); + if (!has_fields) { + Py_RETURN_FALSE; + } + + Safe_PyObjectPtr fields = make_safe(PyObject_GetAttrString(o, "_fields")); + int is_instance = PyObject_IsInstance(fields.get(), CollectionsSequenceType); + if (is_instance == 0) { + Py_RETURN_FALSE; + } else if (is_instance == -1) { + return nullptr; + } + + Safe_PyObjectPtr seq = make_safe(PySequence_Fast(fields.get(), "")); + const Py_ssize_t s = PySequence_Fast_GET_SIZE(seq.get()); + for (Py_ssize_t i = 0; i < s; ++i) { + // PySequence_Fast_GET_ITEM returns borrowed ref + PyObject* elem = PySequence_Fast_GET_ITEM(seq.get(), i); + if (!IsString(elem)) { + Py_RETURN_FALSE; + } + } + + Py_RETURN_TRUE; +} + +PyObject* SameNamedtuples(PyObject* o1, PyObject* o2) { + PyObject* f1 = PyObject_GetAttrString(o1, "_fields"); + PyObject* f2 = PyObject_GetAttrString(o2, "_fields"); + if (f1 == nullptr || f2 == nullptr) { + Py_XDECREF(f1); + Py_XDECREF(f2); + PyErr_SetString( + PyExc_RuntimeError, + "Expected namedtuple-like objects (that have _fields attr)"); + return nullptr; + } + + if (PyObject_RichCompareBool(f1, f2, Py_NE)) { + Py_RETURN_FALSE; + } + + if (GetClassName(o1).compare(GetClassName(o2)) == 0) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +PyObject* AssertSameStructure(PyObject* o1, PyObject* o2, bool check_types) { + string error_msg; + bool is_type_error = false; + AssertSameStructureHelper(o1, o2, check_types, &error_msg, &is_type_error); + if (!error_msg.empty()) { + PyErr_SetString( + is_type_error ? PyExc_TypeError : PyExc_ValueError, + tensorflow::strings::StrCat( + "The two structures don't have the same nested structure.\n\n", + "First structure: ", PyObjectToString(o1), "\n\nSecond structure: ", + PyObjectToString(o2), "\n\nMore specifically: ", error_msg) + .c_str()); + return nullptr; + } + Py_RETURN_NONE; +} + } // namespace swig } // namespace tensorflow diff --git a/tensorflow/python/util/util.h b/tensorflow/python/util/util.h index 2af71dc753..c325baa5f8 100644 --- a/tensorflow/python/util/util.h +++ b/tensorflow/python/util/util.h @@ -33,6 +33,57 @@ namespace swig { // dict. bool IsSequence(PyObject* o); +// Implements the same interface as tensorflow.util.nest._is_namedtuple +// Returns Py_True iff `instance` should be considered a `namedtuple`. +// +// Args: +// instance: An instance of a Python object. +// strict: If True, `instance` is considered to be a `namedtuple` only if +// it is a "plain" namedtuple. For instance, a class inheriting +// from a `namedtuple` will be considered to be a `namedtuple` +// iff `strict=False`. +// +// Returns: +// True if `instance` is a `namedtuple`. +PyObject* IsNamedtuple(PyObject* o, bool strict); + +// Implements the same interface as tensorflow.util.nest._same_namedtuples +// Returns Py_True iff the two namedtuples have the same name and fields. +// Raises RuntimeError if `o1` or `o2` don't look like namedtuples (don't have +// '_fields' attribute). +PyObject* SameNamedtuples(PyObject* o1, PyObject* o2); + +// Asserts that two structures are nested in the same way. +// +// Note that namedtuples with identical name and fields are always considered +// to have the same shallow structure (even with `check_types=True`). +// For intance, this code will print `True`: +// +// ```python +// def nt(a, b): +// return collections.namedtuple('foo', 'a b')(a, b) +// print(assert_same_structure(nt(0, 1), nt(2, 3))) +// ``` +// +// Args: +// nest1: an arbitrarily nested structure. +// nest2: an arbitrarily nested structure. +// check_types: if `true`, types of sequences are checked as +// well, including the keys of dictionaries. If set to `false`, for example +// a list and a tuple of objects will look the same if they have the same +// size. Note that namedtuples with identical name and fields are always +// considered to have the same shallow structure. +// +// Raises: +// ValueError: If the two structures do not have the same number of elements or +// if the two structures are not nested in the same way. +// TypeError: If the two structures differ in the type of sequence in any of +// their substructures. Only possible if `check_types` is `True`. +// +// Returns: +// Py_None on success, nullptr on error. +PyObject* AssertSameStructure(PyObject* o1, PyObject* o2, bool check_types); + // Implements the same interface as tensorflow.util.nest.flatten // // Returns a flat list from a given nested structure. diff --git a/tensorflow/python/util/util.i b/tensorflow/python/util/util.i index d69084fc00..b7f201b6fe 100644 --- a/tensorflow/python/util/util.i +++ b/tensorflow/python/util/util.i @@ -34,6 +34,15 @@ limitations under the License. %unignore tensorflow::swig::IsSequence; %noexception tensorflow::swig::IsSequence; +%unignore tensorflow::swig::IsNamedtuple; +%noexception tensorflow::swig::IsNamedtuple; + +%unignore tensorflow::swig::SameNamedtuples; +%noexception tensorflow::swig::SameNamedtuples; + +%unignore tensorflow::swig::AssertSameStructure; +%noexception tensorflow::swig::AssertSameStructure; + %unignore tensorflow::swig::Flatten; %noexception tensorflow::swig::Flatten; -- GitLab From 59a12553545c3d8f957a1a6e618561d4228f7f59 Mon Sep 17 00:00:00 2001 From: Smit Hinsu Date: Wed, 28 Mar 2018 18:26:46 -0700 Subject: [PATCH 387/906] Relax CuDNN version requirements because CuDNN is backwards compatible within a major release starting with CuDNN 7.0 PiperOrigin-RevId: 190869028 --- tensorflow/stream_executor/BUILD | 6 +- tensorflow/stream_executor/cuda/cuda_dnn.cc | 87 +++++++++++++------ .../stream_executor/cuda/cudnn_version.cc | 42 +++++++++ .../stream_executor/cuda/cudnn_version.h | 51 +++++++++++ .../cuda/cudnn_version_test.cc | 75 ++++++++++++++++ 5 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 tensorflow/stream_executor/cuda/cudnn_version.cc create mode 100644 tensorflow/stream_executor/cuda/cudnn_version.h create mode 100644 tensorflow/stream_executor/cuda/cudnn_version_test.cc diff --git a/tensorflow/stream_executor/BUILD b/tensorflow/stream_executor/BUILD index 1865240014..27cdb860fe 100644 --- a/tensorflow/stream_executor/BUILD +++ b/tensorflow/stream_executor/BUILD @@ -56,7 +56,10 @@ cc_library( [ "cuda/*.cc", ], - exclude = ["cuda/cuda_platform_id.cc"], + exclude = [ + "cuda/*_test.cc", + "cuda/cuda_platform_id.cc", + ], ), ), copts = select({ @@ -72,6 +75,7 @@ cc_library( ":stream_executor", "//tensorflow/core:lib", "//tensorflow/core/kernels:ops_util", + "@com_google_absl//absl/strings", "@local_config_cuda//cuda:cuda_headers", ] + if_cuda_is_configured([ "//tensorflow/core:cuda", diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index ab5e6590e0..1aea0485fd 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -18,7 +18,9 @@ limitations under the License. #include #include +#include "absl/strings/str_cat.h" #include "third_party/eigen3/Eigen/Core" +#include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/util/env_var.h" #include "tensorflow/stream_executor/cuda/cuda_activation.h" #include "tensorflow/stream_executor/cuda/cuda_diagnostics.h" @@ -27,6 +29,7 @@ limitations under the License. #include "tensorflow/stream_executor/cuda/cuda_platform_id.h" #include "tensorflow/stream_executor/cuda/cuda_stream.h" #include "tensorflow/stream_executor/cuda/cuda_timer.h" +#include "tensorflow/stream_executor/cuda/cudnn_version.h" #include "tensorflow/stream_executor/dnn.h" #include "tensorflow/stream_executor/lib/env.h" #include "tensorflow/stream_executor/lib/error.h" @@ -55,15 +58,6 @@ NarrowT CheckedNarrowing(const WideT& wide) { return narrow; } -// Returns the "Compatibility" version number from the CuDNN version number. -// This is the number that tries to indicate ABI compatibility. -// -// For example, if cudnn_version is 5107, the compatibility version -// number will be 5100. -size_t cudnnCompatibilityVersion(size_t cudnn_version) { - return (cudnn_version / 100) * 100; -} - } // namespace namespace perftools { @@ -109,6 +103,22 @@ string ToString(cudnnStatus_t status) { } } +#if CUDNN_VERSION >= 6000 +string ToString(libraryPropertyType type) { + switch (type) { + case MAJOR_VERSION: + return "MAJOR_VERSION"; + case MINOR_VERSION: + return "MINOR_VERSION"; + case PATCH_LEVEL: + return "PATCH_LEVEL"; + default: + return absl::StrCat( + "(type), ">"); + } +} +#endif + template cudnnDataType_t GetCudnnDataType(); @@ -360,6 +370,34 @@ cudnnConvolutionBwdFilterAlgo_t ToConvBackwardFilterAlgo( } } +#if CUDNN_VERSION >= 6000 +port::Status GetCudnnProperty(libraryPropertyType type, int* value) { + cudnnStatus_t status = cudnnGetProperty(type, value); + if (status != CUDNN_STATUS_SUCCESS) { + const string error = + absl::StrCat("cudnnGetProperty failed for type: ", ToString(type), + " with status: ", ToString(status)); + LOG(ERROR) << error; + return port::Status{port::error::INTERNAL, error}; + } + return port::Status::OK(); +} +#endif + +port::Status GetLoadedCudnnVersion(CudnnVersion* version) { +#if CUDNN_VERSION >= 6000 + TF_RETURN_IF_ERROR(GetCudnnProperty(MAJOR_VERSION, &version->major_version)); + TF_RETURN_IF_ERROR(GetCudnnProperty(MINOR_VERSION, &version->minor_version)); + TF_RETURN_IF_ERROR(GetCudnnProperty(PATCH_LEVEL, &version->patch_level)); +#else + size_t loaded_version = ::cudnnGetVersion(); + version->major_version = loaded_version / 1000; + version->minor_version = (loaded_version / 100) % 10; + version->patch_level = loaded_version % 100; +#endif + return port::Status::OK(); +} + } // namespace CudnnSupport::CudnnSupport(CUDAExecutor* parent) @@ -376,24 +414,19 @@ port::Status CudnnSupport::Init() { auto status = wrap::cudnnCreate( parent_, reinterpret_cast(&dnn_handle_)); if (status == CUDNN_STATUS_SUCCESS) { - // Check whether loaded version of CuDNN matches what the source - // was built with. - size_t loaded_version = ::cudnnGetVersion(); - size_t loaded_compat_version = cudnnCompatibilityVersion(loaded_version); - size_t compiled_compat_version = cudnnCompatibilityVersion(CUDNN_VERSION); - bool library_loaded_matches_source = - (loaded_compat_version == compiled_compat_version); - if (!library_loaded_matches_source) { - const string error = - port::StrCat("Loaded runtime CuDNN library: ", loaded_version, - " (compatibility version ", loaded_compat_version, - ") but source was compiled with ", CUDNN_VERSION, - " (compatibility version ", compiled_compat_version, - "). If using a binary install, upgrade your CuDNN " - "library to match. If building from sources, " - "make sure the library loaded at runtime matches a " - "compatible version specified during compile " - "configuration."); + CudnnVersion source_version(CUDNN_MAJOR, CUDNN_MINOR, CUDNN_PATCHLEVEL); + + CudnnVersion loaded_version; + TF_RETURN_IF_ERROR(GetLoadedCudnnVersion(&loaded_version)); + if (!IsSourceCompatibleWithCudnnLibrary(source_version, loaded_version)) { + const tensorflow::string error = absl::StrCat( + "Loaded runtime CuDNN library: ", loaded_version.ToString(), + " but source was compiled with: ", source_version.ToString(), + ". CuDNN library major and minor version needs to match or have " + "higher minor version in case of CuDNN 7.0 or later version. If " + "using a binary install, upgrade your CuDNN library. If building " + "from sources, make sure the library loaded at runtime is compatible " + "with the version specified during compile configuration."); LOG(ERROR) << error; return port::Status{port::error::INTERNAL, error}; } diff --git a/tensorflow/stream_executor/cuda/cudnn_version.cc b/tensorflow/stream_executor/cuda/cudnn_version.cc new file mode 100644 index 0000000000..5591801aae --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_version.cc @@ -0,0 +1,42 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/stream_executor/cuda/cudnn_version.h" + +namespace perftools { +namespace gputools { +namespace cuda { + +bool IsSourceCompatibleWithCudnnLibrary(CudnnVersion source_version, + CudnnVersion loaded_version) { + // Major version is neither forward or backward compatible and therefore major + // versions needs to match between source and library. + // + // Minor version is backward-compatible beginning with CuDNN 7 and therefore + // minor version of library needs to be same or higher. + // + // Patch releases are always forward and backward compatible and therefore + // need not match. + if (loaded_version.major_version != source_version.major_version) { + return false; + } + return ((loaded_version.minor_version == source_version.minor_version) || + (source_version.major_version >= 7 && + loaded_version.minor_version >= source_version.minor_version)); +} + +} // namespace cuda +} // namespace gputools +} // namespace perftools diff --git a/tensorflow/stream_executor/cuda/cudnn_version.h b/tensorflow/stream_executor/cuda/cudnn_version.h new file mode 100644 index 0000000000..058cc87bfa --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_version.h @@ -0,0 +1,51 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDNN_VERSION_H_ +#define TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDNN_VERSION_H_ + +#include + +#include "absl/strings/str_join.h" + +namespace perftools { +namespace gputools { +namespace cuda { + +struct CudnnVersion { + CudnnVersion() = default; + + CudnnVersion(int major, int minor, int patch) + : major_version(major), minor_version(minor), patch_level(patch) {} + + std::string ToString() const { + return absl::StrJoin({major_version, minor_version, patch_level}, "."); + } + + int major_version; + int minor_version; + int patch_level; +}; + +// Returns true if the given source CuDNN version is compatible with the given +// loaded version. +bool IsSourceCompatibleWithCudnnLibrary(CudnnVersion source_version, + CudnnVersion loaded_version); + +} // namespace cuda +} // namespace gputools +} // namespace perftools + +#endif // TENSORFLOW_STREAM_EXECUTOR_CUDA_CUDNN_VERSION_H_ diff --git a/tensorflow/stream_executor/cuda/cudnn_version_test.cc b/tensorflow/stream_executor/cuda/cudnn_version_test.cc new file mode 100644 index 0000000000..230adafeb1 --- /dev/null +++ b/tensorflow/stream_executor/cuda/cudnn_version_test.cc @@ -0,0 +1,75 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/stream_executor/cuda/cudnn_version.h" + +#include "testing/base/public/gunit.h" +#include "tensorflow/core/platform/test.h" + +namespace perftools { +namespace gputools { +namespace cuda { +namespace { + +TEST(CuDNNVersion, ToString) { + CudnnVersion version(7, 0, 12); + EXPECT_EQ(version.ToString(), "7.0.12"); +} + +TEST(IsSourceCompatibleWithCudnnLibraryTest, Basic) { + // Returns true if both major and minor versions are matching and even if the + // patch versions are not matching. + EXPECT_TRUE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(7, 0, 12), + /*loaded_version=*/CudnnVersion(7, 0, 14))); + EXPECT_TRUE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(6, 1, 14), + /*loaded_version=*/CudnnVersion(6, 1, 00))); + + // Returns false if major versions are not matching as they are neither + // forward or backward compatible. + EXPECT_FALSE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(7, 0, 12), + /*loaded_version=*/CudnnVersion(6, 1, 14))); + EXPECT_FALSE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(8, 1, 15), + /*loaded_version=*/CudnnVersion(7, 0, 14))); + + // Returns true if the loaded version is equal or higher because minor version + // are backward compatible with CuDNN version 7. + EXPECT_TRUE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(7, 0, 14), + /*loaded_version=*/CudnnVersion(7, 1, 14))); + EXPECT_TRUE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(7, 0, 14), + /*loaded_version=*/CudnnVersion(7, 1, 15))); + EXPECT_FALSE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(7, 1, 15), + /*loaded_version=*/CudnnVersion(7, 0, 14))); + + // Returns false if minor versions are not matching for version 6. Before + // version 7, minor versions are also neither forward or backward compatible. + EXPECT_FALSE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(6, 0, 14), + /*loaded_version=*/CudnnVersion(6, 1, 15))); + EXPECT_FALSE(IsSourceCompatibleWithCudnnLibrary( + /*source_version=*/CudnnVersion(6, 1, 14), + /*loaded_version=*/CudnnVersion(6, 0, 14))); +} + +} // namespace +} // namespace cuda +} // namespace gputools +} // namespace perftools -- GitLab From 2b41d75654012f917cda1b54aee090d73086ab84 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 18:54:09 -0700 Subject: [PATCH 388/906] [XLA] Redesign: implement GetComputationStats. PiperOrigin-RevId: 190871262 --- tensorflow/compiler/xla/client/client.cc | 47 ++++++++++++++++++++-- tensorflow/compiler/xla/client/client.h | 2 + tensorflow/compiler/xla/service/service.cc | 20 ++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/tensorflow/compiler/xla/client/client.cc b/tensorflow/compiler/xla/client/client.cc index a857c4ff0b..c4c8894374 100644 --- a/tensorflow/compiler/xla/client/client.cc +++ b/tensorflow/compiler/xla/client/client.cc @@ -276,7 +276,12 @@ StatusOr> Client::Execute( if (execution_profile != nullptr) { *execution_profile = response.profile(); - // TODO(b/74197823): Get execution stats for the graph and VLOG(1) them. + if (VLOG_IS_ON(1)) { + TF_ASSIGN_OR_RETURN( + auto execution_stats, + ExecutionStatsAsString(computation, response.profile())); + VLOG(1) << execution_stats; + } } return MakeUnique(stub_, response.output()); @@ -402,8 +407,22 @@ StatusOr Client::GetComputationStats( StatusOr Client::GetComputationStats( const XlaComputation& computation, const DebugOptions& debug_options) const { - return Unimplemented( - "GetComputationStats is not yet implemented for XlaComputation"); + ComputationGraphStatsRequest request; + + // TODO(b/74197823): Find a way to avoid the copy of the hlo proto. + *request.mutable_computation() = computation.proto(); + *request.mutable_debug_options() = debug_options; + ComputationStatsResponse response; + + VLOG(1) << "making computation graph stats request"; + Status s = stub_->GetComputationGraphStats(&request, &response); + VLOG(1) << "done with request"; + + if (!s.ok()) { + return s; + } + CHECK(response.has_stats()); + return response.stats(); } StatusOr> Client::GetComputationShape( @@ -467,6 +486,28 @@ StatusOr Client::ExecutionStatsAsString( return string("[Execution Statistics] not available."); } +StatusOr Client::ExecutionStatsAsString( + const XlaComputation& computation, const ExecutionProfile& profile) { + TF_ASSIGN_OR_RETURN( + auto computation_stats, + GetComputationStats(computation, + legacy_flags::GetDebugOptionsFromFlags())); + int64 total_flops = + computation_stats.flop_count() + computation_stats.transcendental_count(); + if (profile.compute_time_ns() > 0) { + int64 nanoseconds = profile.compute_time_ns(); + int64 cycle_count = profile.compute_cycle_count(); + double gflops = total_flops / nanoseconds; + return tensorflow::strings::StrCat( + "[Execution Statistics] flop count: ", computation_stats.flop_count(), + ", transcendental count: ", computation_stats.transcendental_count(), + ", compute execution time: ", nanoseconds, " nsec", + ", compute cycles: ", cycle_count, ", performance: ", gflops, + "gflop/s"); + } + return string("[Execution Statistics] not available."); +} + StatusOr Client::CreateChannelHandle() { CreateChannelHandleRequest request; CreateChannelHandleResponse response; diff --git a/tensorflow/compiler/xla/client/client.h b/tensorflow/compiler/xla/client/client.h index 226b788d54..05d707dab1 100644 --- a/tensorflow/compiler/xla/client/client.h +++ b/tensorflow/compiler/xla/client/client.h @@ -241,6 +241,8 @@ class Client { // ExecutionProfile returned from an execution of the computation. StatusOr ExecutionStatsAsString(const Computation& computation, const ExecutionProfile& profile); + StatusOr ExecutionStatsAsString(const XlaComputation& computation, + const ExecutionProfile& profile); ServiceInterface* stub_; // Stub that this client is connected on. diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index af05e3f516..ca8071b7bb 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -1452,7 +1452,25 @@ tensorflow::Status Service::GetComputationStats( tensorflow::Status Service::GetComputationGraphStats( const ComputationGraphStatsRequest* arg, ComputationStatsResponse* result) { - return Unimplemented("get-computation-graph-stats is not yet implemented"); + HloModuleConfig config; + config.set_debug_options(arg->debug_options()); + TF_ASSIGN_OR_RETURN(std::unique_ptr module, + HloModule::CreateFromProto(arg->computation(), config)); + + hlo_graph_dumper::MaybeDumpHloModule(*module, + "computation statistics subject"); + + // Run HLO analysis to get the computation statistics. + HloCostAnalysis analysis( + execute_backend_->compiler()->ShapeSizeBytesFunction()); + + TF_RETURN_IF_ERROR(module->entry_computation()->Accept(&analysis)); + + ComputationStats stats; + stats.set_flop_count(analysis.flop_count()); + stats.set_transcendental_count(analysis.transcendental_count()); + *result->mutable_stats() = stats; + return tensorflow::Status::OK(); } template -- GitLab From 3e51f9ede54bc61a8d4f7797992ab78140467d08 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Wed, 28 Mar 2018 18:59:13 -0700 Subject: [PATCH 389/906] Default to disable including the coordinator in the job --- .../cluster_resolver/python/training/tpu_cluster_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py index 300b19733e..95c5c920aa 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py +++ b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py @@ -73,7 +73,7 @@ class TPUClusterResolver(ClusterResolver): zone=None, project=None, job_name='worker', - coordinator_name='coordinator', + coordinator_name=None, coordinator_address=None, credentials='default', service=None): -- GitLab From 991e205a78f67ce21b0918613a45cfd7c3e348fd Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Thu, 29 Mar 2018 10:05:43 +0800 Subject: [PATCH 390/906] Fix the incorect format of math equation in factorization_ops (#18054) * Fix the incorect format of math equation in factorization_ops * Fix minor intent format * Fix pylint issues * Fix serveral minor intent --- .../python/ops/factorization_ops.py | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/tensorflow/contrib/factorization/python/ops/factorization_ops.py b/tensorflow/contrib/factorization/python/ops/factorization_ops.py index 8e0ed1d80e..3f3e3e0f25 100644 --- a/tensorflow/contrib/factorization/python/ops/factorization_ops.py +++ b/tensorflow/contrib/factorization/python/ops/factorization_ops.py @@ -51,9 +51,9 @@ class WALSModel(object): r"""A model for Weighted Alternating Least Squares matrix factorization. It minimizes the following loss function over U, V: - \\( - \|\sqrt W \odot (A - U V^T) \|_F^2 + \lambda (\|U\|_F^2 + \|V\|_F^2) - )\\ + $$ + \|\sqrt W \odot (A - U V^T)\|_F^2 + \lambda (\|U\|_F^2 + \|V\|_F^2) + $$ where, A: input matrix, W: weight matrix. Note that the (element-wise) square root of the weights @@ -61,12 +61,12 @@ class WALSModel(object): U, V: row_factors and column_factors matrices, \\(\lambda)\\: regularization. Also we assume that W is of the following special form: - \\( W_{ij} = W_0 + R_i * C_j )\\ if \\(A_{ij} \ne 0)\\, - \\(W_{ij} = W_0)\\ otherwise. + \\( W_{ij} = W_0 + R_i * C_j \\) if \\(A_{ij} \ne 0\\), + \\(W_{ij} = W_0\\) otherwise. where, - \\(W_0)\\: unobserved_weight, - \\(R_i)\\: row_weights, - \\(C_j)\\: col_weights. + \\(W_0\\): unobserved_weight, + \\(R_i\\): row_weights, + \\(C_j\\): col_weights. Note that the current implementation supports two operation modes: The default mode is for the condition where row_factors and col_factors can individually @@ -82,14 +82,15 @@ class WALSModel(object): normalized as follows: _, _, unregularized_loss, regularization, sum_weights = update_row_factors(sp_input) - if sp_input contains the rows {A_i, i \in I}, and the input matrix A has n - total rows, then the minibatch loss = unregularized_loss + regularization is - \\( + if sp_input contains the rows \\({A_i, i \in I}\\), and the input matrix A + has n total rows, then the minibatch loss = unregularized_loss + + regularization is + $$ (\|\sqrt W_I \odot (A_I - U_I V^T)\|_F^2 + \lambda \|U_I\|_F^2) * n / |I| + \lambda \|V\|_F^2 - )\\ + $$ The sum_weights tensor contains the normalized sum of weights - sum(W_I) * n / |I|. + \\(sum(W_I) * n / |I|\\). A typical usage example (pseudocode): @@ -217,13 +218,13 @@ class WALSModel(object): - When set to None, w_ij = unobserved_weight, which simplifies to ALS. Note that col_weights must also be set to "None" in this case. - If it is a list of lists of non-negative real numbers, it needs to be - in the form of [[w_0, w_1, ...], [w_k, ... ], [...]], with the number of - inner lists matching the number of row factor shards and the elements in - each inner list are the weights for the rows of the corresponding row - factor shard. In this case, w_ij = unobserved_weight + - row_weights[i] * col_weights[j]. + in the form of \\([[w_0, w_1, ...], [w_k, ... ], [...]]\\), with the + number of inner lists matching the number of row factor shards and the + elements in each inner list are the weights for the rows of the + corresponding row factor shard. In this case, \\(w_ij\\) = + unobserved_weight + row_weights[i] * col_weights[j]. - If this is a single non-negative real number, this value is used for - all row weights and w_ij = unobserved_weight + row_weights * + all row weights and \\(w_ij\\) = unobserved_weight + row_weights * col_weights[j]. Note that it is allowed to have row_weights as a list while col_weights a single number or vice versa. @@ -665,18 +666,18 @@ class WALSModel(object): factors. unregularized_loss: A tensor (scalar) that contains the normalized minibatch loss corresponding to sp_input, without the regularization - term. If sp_input contains the rows {A_{i, :}, i \in I}, and the input - matrix A has n total rows, then the unregularized loss is: - (\|\sqrt W_I \odot (A_I - U_I V^T)\|_F^2 * n / |I| + term. If sp_input contains the rows \\({A_{i, :}, i \in I}\\), and the + input matrix A has n total rows, then the unregularized loss is: + \\(\|\sqrt W_I \odot (A_I - U_I V^T)\|_F^2 * n / |I|\\) The total loss is unregularized_loss + regularization. regularization: A tensor (scalar) that contains the normalized regularization term for the minibatch loss corresponding to sp_input. - If sp_input contains the rows {A_{i, :}, i \in I}, and the input matrix - A has n total rows, then the regularization term is: - \lambda \|U_I\|_F^2) * n / |I| + \lambda \|V\|_F^2. + If sp_input contains the rows \\({A_{i, :}, i \in I}\\), and the input + matrix A has n total rows, then the regularization term is: + \\(\lambda \|U_I\|_F^2) * n / |I| + \lambda \|V\|_F^2\\). sum_weights: The sum of the weights W_I corresponding to sp_input, - normalized by a factor of n / |I|. The root weighted squared error is: - \sqrt(unregularized_loss / sum_weights). + normalized by a factor of \\(n / |I|\\). The root weighted squared + error is: \sqrt(unregularized_loss / sum_weights). """ return self._process_input_helper( True, sp_input=sp_input, transpose_input=transpose_input) @@ -698,18 +699,18 @@ class WALSModel(object): factors. unregularized_loss: A tensor (scalar) that contains the normalized minibatch loss corresponding to sp_input, without the regularization - term. If sp_input contains the columns {A_{:, j}, j \in J}, and the - input matrix A has m total columns, then the unregularized loss is: - (\|\sqrt W_J \odot (A_J - U V_J^T)\|_F^2 * m / |I| + term. If sp_input contains the columns \\({A_{:, j}, j \in J}\\), and + the input matrix A has m total columns, then the unregularized loss is: + \\(\|\sqrt W_J \odot (A_J - U V_J^T)\|_F^2 * m / |I|\\) The total loss is unregularized_loss + regularization. regularization: A tensor (scalar) that contains the normalized regularization term for the minibatch loss corresponding to sp_input. - If sp_input contains the columns {A_{:, j}, j \in J}, and the input - matrix A has m total columns, then the regularization term is: - \lambda \|V_J\|_F^2) * m / |J| + \lambda \|U\|_F^2. + If sp_input contains the columns \\({A_{:, j}, j \in J}\\), and the + input matrix A has m total columns, then the regularization term is: + \\(\lambda \|V_J\|_F^2) * m / |J| + \lambda \|U\|_F^2\\). sum_weights: The sum of the weights W_J corresponding to sp_input, - normalized by a factor of m / |J|. The root weighted squared error is: - \sqrt(unregularized_loss / sum_weights). + normalized by a factor of \\(m / |J|\\). The root weighted squared + error is: \sqrt(unregularized_loss / sum_weights). """ return self._process_input_helper( False, sp_input=sp_input, transpose_input=transpose_input) @@ -720,8 +721,8 @@ class WALSModel(object): projection_weights=None): """Projects the row factors. - This computes the row embedding u_i for an observed row a_i by solving - one iteration of the update equations. + This computes the row embedding \\(u_i\\) for an observed row \\(a_i\\) by + solving one iteration of the update equations. Args: sp_input: A SparseTensor representing a set of rows. Please note that the @@ -753,8 +754,8 @@ class WALSModel(object): projection_weights=None): """Projects the column factors. - This computes the column embedding v_j for an observed column a_j by solving - one iteration of the update equations. + This computes the column embedding \\(v_j\\) for an observed column + \\(a_j\\) by solving one iteration of the update equations. Args: sp_input: A SparseTensor representing a set of columns. Please note that @@ -938,7 +939,7 @@ class WALSModel(object): loss_sp_input = (sparse_ops.sparse_transpose(new_sp_input) if transpose_input else new_sp_input) # sp_approx is the low rank estimate of the input matrix, formed by - # computing the product for (i, j) in loss_sp_input.indices. + # computing the product <\\(u_i, v_j\\)> for (i, j) in loss_sp_input.indices. sp_approx_vals = gen_factorization_ops.masked_matmul( new_left_values, right, -- GitLab From a5a90e6b55c19bd14d5effa5cb1695ddbe31026f Mon Sep 17 00:00:00 2001 From: Suharsh Sivakumar Date: Wed, 28 Mar 2018 19:21:08 -0700 Subject: [PATCH 391/906] Relax limitations on rerouting graph outputs. - Allow multiple outputs of output_tensors in fold_batch_norms. - Allow duplicate consumers in quantize. - I also quick a fix issue for matching final layers that have batch norm. PiperOrigin-RevId: 190873003 --- .../quantize/python/fold_batch_norms.py | 6 +++--- tensorflow/contrib/quantize/python/quantize.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tensorflow/contrib/quantize/python/fold_batch_norms.py b/tensorflow/contrib/quantize/python/fold_batch_norms.py index 5750be6f4c..4a8f8a04cc 100644 --- a/tensorflow/contrib/quantize/python/fold_batch_norms.py +++ b/tensorflow/contrib/quantize/python/fold_batch_norms.py @@ -134,9 +134,9 @@ def _FoldFusedBatchNorms(graph, is_training, freeze_batch_norm_delay): nodes_modified_count = graph_editor.reroute_ts(bias_add_tensor, match.output_tensor) - if nodes_modified_count != 1: - raise ValueError( - 'Unexpected inputs to op: %s' % match.output_tensor.name) + if nodes_modified_count == 0: + raise ValueError('Folding batch norms failed, %s had no outputs.' % + match.output_tensor.name) def _FindFusedBatchNorms(graph): diff --git a/tensorflow/contrib/quantize/python/quantize.py b/tensorflow/contrib/quantize/python/quantize.py index 019d123a68..2889016a84 100644 --- a/tensorflow/contrib/quantize/python/quantize.py +++ b/tensorflow/contrib/quantize/python/quantize.py @@ -305,7 +305,8 @@ def _FindLayersToQuantize(graph): # the output of the final BiasAdd must be quantized. So we treat the BiasAdd # as the 'activation_op' in the _LayerMatch, to ensure that it's output is # quantized. - final_layer_matcher = graph_matcher.GraphMatcher(bias_add_pattern) + final_layer_matcher = graph_matcher.GraphMatcher( + graph_matcher.OneofPattern([bias_add_pattern, folded_bias_add_pattern])) for match_result in final_layer_matcher.match_graph(graph): layer_op = match_result.get_op(layer_pattern) weight_tensor = match_result.get_tensor(weight_identity_pattern) @@ -463,11 +464,16 @@ def _InsertQuantOp(context, lambda: inputs, name=name_prefix + '/delayed_quant') - nodes_modified_count = graph_editor.reroute_ts( - [quant], [inputs], can_modify=consumers) - if nodes_modified_count != len(consumers): - raise ValueError('Some inputs not quantized for ops: [%s]' % ', '.join( - [consumer.name for consumer in consumers])) + if consumers: + tensors_modified_count = graph_editor.reroute_ts( + [quant], [inputs], can_modify=consumers) + # Some operations can have multiple output tensors going to the same + # consumer. Since consumers is a set, we need to ensure that + # tensors_modified_count is greater than or equal to the length of the set + # of consumers. + if tensors_modified_count < len(consumers): + raise ValueError('No inputs quantized for ops: [%s]' % ', '.join( + [consumer.name for consumer in consumers])) def _GetContextFromOp(op): -- GitLab From aef7d8b3e877924973e3d8d8e6266ba7b8322a66 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Wed, 28 Mar 2018 19:27:36 -0700 Subject: [PATCH 392/906] Fix the test --- .../python/training/tpu_cluster_resolver_test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py index 48c3f6bb4f..e1e3e6867a 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py @@ -117,7 +117,8 @@ class TPUClusterResolverTest(test.TestCase): zone=None, tpu=['test-tpu-1'], credentials=None, - service=self.mock_service_client(tpu_map=tpu_map)) + service=self.mock_service_client(tpu_map=tpu_map), + coordinator_name='coordinator') actual_cluster_spec = tpu_cluster_resolver.cluster_spec() expected_proto = """ @@ -170,6 +171,7 @@ class TPUClusterResolverTest(test.TestCase): project='test-project', zone='us-central1-c', tpu=['test-tpu-1'], + coordinator_name='coordinator', coordinator_address='10.128.1.5:10203', credentials=None, service=self.mock_service_client(tpu_map=tpu_map)) @@ -196,6 +198,7 @@ class TPUClusterResolverTest(test.TestCase): project='test-project', zone='us-central1-c', tpu='test-tpu-1', + coordinator_name='coordinator', coordinator_address='10.128.1.5:10203', credentials=None, service=self.mock_service_client(tpu_map=tpu_map)) @@ -239,7 +242,8 @@ class TPUClusterResolverTest(test.TestCase): tpu_cluster_resolver = TPUClusterResolver( tpu='test-tpu-1', credentials=None, - service=self.mock_service_client(tpu_map=tpu_map)) + service=self.mock_service_client(tpu_map=tpu_map), + coordinator_name='coordinator') actual_cluster_spec = tpu_cluster_resolver.cluster_spec() expected_proto = """ -- GitLab From 789e442513e85ab1caeb1e03997b0aafa3cd76d7 Mon Sep 17 00:00:00 2001 From: Derek Murray Date: Wed, 28 Mar 2018 20:44:51 -0700 Subject: [PATCH 393/906] [tf.data] Maintain a reference on the FunctionBufferingResource while a get-next operation is active. Previously, the reference count on a FunctionBufferingResource could drop to 0 and it could be deleted (e.g. by a DestroyResourceOp) while a get-next operation is active on it. This would lead to use-after-free errors. PiperOrigin-RevId: 190878208 --- tensorflow/contrib/data/kernels/prefetching_kernels.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/data/kernels/prefetching_kernels.cc b/tensorflow/contrib/data/kernels/prefetching_kernels.cc index f51570db85..2afb8dbbf4 100644 --- a/tensorflow/contrib/data/kernels/prefetching_kernels.cc +++ b/tensorflow/contrib/data/kernels/prefetching_kernels.cc @@ -374,25 +374,27 @@ class FunctionBufferingResourceGetNextOp : public AsyncOpKernel { OP_REQUIRES_OK_ASYNC( ctx, LookupResource(ctx, handle, &buffer), done); - core::ScopedUnref s(buffer); if (buffer->Finished()) { + buffer->Unref(); ctx->SetStatus(errors::OutOfRange("end_of_sequence")); done(); return; } FunctionBufferCallback callback = - [ctx, done](const BufferElement& buffer_element) { + [ctx, buffer, done](const BufferElement& buffer_element) { Status s = buffer_element.status; if (!s.ok()) { ctx->SetStatus(s); + buffer->Unref(); done(); return; } for (size_t i = 0; i < buffer_element.value.size(); ++i) { ctx->set_output(i, buffer_element.value[i]); } + buffer->Unref(); done(); }; buffer->MaybeGet(std::move(callback)); -- GitLab From bb582f1b6fad474bc446c78a6683247a8eb6048e Mon Sep 17 00:00:00 2001 From: Martin Wicke Date: Wed, 28 Mar 2018 20:46:14 -0700 Subject: [PATCH 394/906] Remove all_opensource_files. It's not needed any more. PiperOrigin-RevId: 190878279 --- tensorflow/BUILD | 298 ------------------ tensorflow/c/BUILD | 15 - tensorflow/cc/BUILD | 12 - tensorflow/cc/saved_model/BUILD | 15 - tensorflow/cc/saved_model/python/BUILD | 12 - tensorflow/cc/tools/BUILD | 15 - tensorflow/compiler/aot/BUILD | 14 - tensorflow/compiler/aot/tests/BUILD | 14 - tensorflow/compiler/jit/BUILD | 14 - tensorflow/compiler/jit/graphcycles/BUILD | 14 - tensorflow/compiler/jit/kernels/BUILD | 14 - tensorflow/compiler/jit/legacy_flags/BUILD | 14 - tensorflow/compiler/jit/ops/BUILD | 14 - tensorflow/compiler/plugin/BUILD | 14 - tensorflow/compiler/tests/BUILD | 14 - tensorflow/compiler/tf2xla/BUILD | 14 - tensorflow/compiler/tf2xla/cc/BUILD | 14 - tensorflow/compiler/tf2xla/kernels/BUILD | 14 - tensorflow/compiler/tf2xla/lib/BUILD | 14 - tensorflow/compiler/tf2xla/ops/BUILD | 14 - tensorflow/compiler/xla/BUILD | 12 - tensorflow/compiler/xla/client/BUILD | 14 - tensorflow/compiler/xla/client/lib/BUILD | 14 - .../compiler/xla/client/xla_client/BUILD | 14 - tensorflow/compiler/xla/legacy_flags/BUILD | 14 - tensorflow/compiler/xla/python/BUILD | 12 - tensorflow/compiler/xla/service/BUILD | 14 - tensorflow/compiler/xla/service/cpu/BUILD | 14 - tensorflow/compiler/xla/service/gpu/BUILD | 14 - .../xla/service/gpu/llvm_gpu_backend/BUILD | 14 - .../compiler/xla/service/interpreter/BUILD | 11 - tensorflow/compiler/xla/service/llvm_ir/BUILD | 14 - tensorflow/compiler/xla/tests/BUILD | 14 - tensorflow/compiler/xla/tools/BUILD | 14 - tensorflow/compiler/xla/tools/parser/BUILD | 14 - tensorflow/contrib/BUILD | 12 - tensorflow/contrib/all_reduce/BUILD | 13 - tensorflow/contrib/android/BUILD | 14 - tensorflow/contrib/batching/BUILD | 11 - tensorflow/contrib/batching/test_util/BUILD | 11 - tensorflow/contrib/batching/util/BUILD | 12 - tensorflow/contrib/bayesflow/BUILD | 12 - tensorflow/contrib/boosted_trees/BUILD | 9 - .../boosted_trees/estimator_batch/BUILD | 9 - tensorflow/contrib/boosted_trees/lib/BUILD | 11 - tensorflow/contrib/boosted_trees/proto/BUILD | 11 - .../contrib/boosted_trees/resources/BUILD | 11 - tensorflow/contrib/cloud/BUILD | 12 - tensorflow/contrib/cloud/kernels/BUILD | 14 - tensorflow/contrib/cluster_resolver/BUILD | 13 - tensorflow/contrib/coder/BUILD | 11 - tensorflow/contrib/compiler/BUILD | 12 - tensorflow/contrib/copy_graph/BUILD | 12 - tensorflow/contrib/crf/BUILD | 12 - tensorflow/contrib/cudnn_rnn/BUILD | 12 - tensorflow/contrib/data/BUILD | 14 - tensorflow/contrib/data/kernels/BUILD | 11 - .../contrib/data/python/kernel_tests/BUILD | 14 - tensorflow/contrib/data/python/ops/BUILD | 12 - tensorflow/contrib/decision_trees/proto/BUILD | 8 - tensorflow/contrib/deprecated/BUILD | 12 - tensorflow/contrib/distributions/BUILD | 12 - tensorflow/contrib/eager/proto/BUILD | 11 - tensorflow/contrib/eager/python/BUILD | 13 - tensorflow/contrib/estimator/BUILD | 12 - tensorflow/contrib/factorization/BUILD | 13 - .../contrib/factorization/examples/BUILD | 11 - .../contrib/factorization/kernels/BUILD | 11 - tensorflow/contrib/feature_column/BUILD | 12 - tensorflow/contrib/ffmpeg/BUILD | 12 - tensorflow/contrib/ffmpeg/default/BUILD | 12 - tensorflow/contrib/framework/BUILD | 12 - tensorflow/contrib/fused_conv/BUILD | 12 - tensorflow/contrib/gan/BUILD | 12 - tensorflow/contrib/gdr/BUILD | 12 - tensorflow/contrib/graph_editor/BUILD | 12 - tensorflow/contrib/grid_rnn/BUILD | 12 - tensorflow/contrib/hooks/BUILD | 11 - .../contrib/hvx/clock_cycle_profiling/BUILD | 12 - .../contrib/hvx/hvx_ops_support_checker/BUILD | 11 - tensorflow/contrib/image/BUILD | 12 - tensorflow/contrib/input_pipeline/BUILD | 11 - .../contrib/input_pipeline/kernels/BUILD | 11 - tensorflow/contrib/integrate/BUILD | 11 - tensorflow/contrib/kafka/BUILD | 14 - tensorflow/contrib/keras/BUILD | 12 - tensorflow/contrib/kernel_methods/BUILD | 12 - tensorflow/contrib/kfac/BUILD | 12 - tensorflow/contrib/kfac/examples/BUILD | 12 - tensorflow/contrib/kfac/examples/tests/BUILD | 12 - .../contrib/kfac/python/kernel_tests/BUILD | 12 - tensorflow/contrib/kfac/python/ops/BUILD | 12 - tensorflow/contrib/labeled_tensor/BUILD | 11 - tensorflow/contrib/layers/BUILD | 12 - tensorflow/contrib/layers/kernels/BUILD | 11 - tensorflow/contrib/learn/BUILD | 12 - .../contrib/learn/python/learn/datasets/BUILD | 12 - tensorflow/contrib/legacy_seq2seq/BUILD | 12 - tensorflow/contrib/libsvm/BUILD | 12 - tensorflow/contrib/linalg/BUILD | 12 - tensorflow/contrib/linear_optimizer/BUILD | 11 - tensorflow/contrib/lite/BUILD | 15 - .../contrib/lite/examples/label_image/BUILD | 12 - tensorflow/contrib/lite/java/BUILD | 12 - .../contrib/lite/java/demo/app/src/main/BUILD | 12 - .../lite/java/demo/app/src/main/assets/BUILD | 12 - .../contrib/lite/java/src/main/native/BUILD | 12 - .../testhelper/java/org/tensorflow/lite/BUILD | 12 - tensorflow/contrib/lite/kernels/BUILD | 12 - .../contrib/lite/kernels/internal/BUILD | 12 - tensorflow/contrib/lite/models/BUILD | 12 - .../contrib/lite/models/smartreply/BUILD | 12 - tensorflow/contrib/lite/nnapi/BUILD | 12 - tensorflow/contrib/lite/python/BUILD | 12 - tensorflow/contrib/lite/schema/BUILD | 12 - tensorflow/contrib/lite/testing/BUILD | 12 - tensorflow/contrib/lite/toco/BUILD | 12 - .../toco/graph_transformations/tests/BUILD | 12 - tensorflow/contrib/lite/toco/python/BUILD | 12 - .../lite/toco/tensorflow_graph_matching/BUILD | 12 - tensorflow/contrib/lite/toco/tflite/BUILD | 12 - tensorflow/contrib/lite/tools/BUILD | 12 - tensorflow/contrib/lookup/BUILD | 12 - tensorflow/contrib/losses/BUILD | 12 - tensorflow/contrib/makefile/BUILD | 9 - tensorflow/contrib/memory_stats/BUILD | 12 - tensorflow/contrib/meta_graph_transform/BUILD | 12 - tensorflow/contrib/metrics/BUILD | 11 - tensorflow/contrib/model_pruning/BUILD | 12 - .../model_pruning/examples/cifar10/BUILD | 12 - tensorflow/contrib/mpi_collectives/BUILD | 12 - tensorflow/contrib/nccl/BUILD | 12 - tensorflow/contrib/nearest_neighbor/BUILD | 12 - tensorflow/contrib/nn/BUILD | 11 - tensorflow/contrib/opt/BUILD | 11 - tensorflow/contrib/periodic_resample/BUILD | 12 - tensorflow/contrib/predictor/BUILD | 12 - tensorflow/contrib/quantization/BUILD | 12 - tensorflow/contrib/quantize/BUILD | 12 - tensorflow/contrib/receptive_field/BUILD | 12 - tensorflow/contrib/reduce_slice_ops/BUILD | 12 - .../contrib/remote_fused_graph/pylib/BUILD | 12 - tensorflow/contrib/resampler/BUILD | 11 - tensorflow/contrib/rnn/BUILD | 13 - tensorflow/contrib/saved_model/BUILD | 12 - .../contrib/saved_model/cc/saved_model/BUILD | 6 - tensorflow/contrib/seq2seq/BUILD | 12 - tensorflow/contrib/session_bundle/BUILD | 12 - .../contrib/session_bundle/example/BUILD | 13 - tensorflow/contrib/signal/BUILD | 12 - tensorflow/contrib/slim/BUILD | 12 - .../contrib/slim/python/slim/data/BUILD | 12 - .../contrib/slim/python/slim/nets/BUILD | 12 - tensorflow/contrib/solvers/BUILD | 13 - tensorflow/contrib/sparsemax/BUILD | 12 - tensorflow/contrib/specs/BUILD | 12 - tensorflow/contrib/staging/BUILD | 12 - tensorflow/contrib/stat_summarizer/BUILD | 12 - tensorflow/contrib/stateless/BUILD | 12 - tensorflow/contrib/summary/BUILD | 12 - tensorflow/contrib/tensor_forest/BUILD | 14 - tensorflow/contrib/tensor_forest/hybrid/BUILD | 12 - .../contrib/tensor_forest/kernels/v4/BUILD | 5 - tensorflow/contrib/tensor_forest/proto/BUILD | 8 - tensorflow/contrib/tensorboard/BUILD | 12 - tensorflow/contrib/tensorboard/db/BUILD | 6 - tensorflow/contrib/tensorrt/BUILD | 12 - tensorflow/contrib/testing/BUILD | 12 - tensorflow/contrib/text/BUILD | 11 - tensorflow/contrib/tfprof/BUILD | 12 - tensorflow/contrib/timeseries/BUILD | 12 - tensorflow/contrib/timeseries/examples/BUILD | 12 - .../timeseries/python/timeseries/BUILD | 12 - .../timeseries/state_space_models/BUILD | 12 - tensorflow/contrib/tpu/BUILD | 13 - tensorflow/contrib/tpu/profiler/BUILD | 12 - tensorflow/contrib/tpu/proto/BUILD | 11 - tensorflow/contrib/training/BUILD | 12 - tensorflow/contrib/util/BUILD | 12 - tensorflow/contrib/verbs/BUILD | 12 - tensorflow/core/BUILD | 14 +- tensorflow/core/api_def/BUILD | 12 - tensorflow/core/common_runtime/eager/BUILD | 15 - tensorflow/core/debug/BUILD | 15 - tensorflow/core/distributed_runtime/BUILD | 12 - tensorflow/core/distributed_runtime/rpc/BUILD | 12 - tensorflow/core/grappler/BUILD | 12 - tensorflow/core/grappler/clusters/BUILD | 12 - tensorflow/core/grappler/costs/BUILD | 12 - tensorflow/core/grappler/inputs/BUILD | 12 - tensorflow/core/grappler/optimizers/BUILD | 12 - tensorflow/core/grappler/utils/BUILD | 12 - tensorflow/core/kernels/BUILD | 12 - tensorflow/core/kernels/batching_util/BUILD | 12 - tensorflow/core/kernels/data/BUILD | 12 - tensorflow/core/kernels/data/sql/BUILD | 12 - tensorflow/core/kernels/fuzzing/BUILD | 12 - tensorflow/core/kernels/hexagon/BUILD | 12 - tensorflow/core/kernels/neon/BUILD | 12 - tensorflow/core/lib/db/BUILD | 6 - tensorflow/core/ops/compat/BUILD | 15 - tensorflow/core/platform/cloud/BUILD | 14 - .../core/platform/default/build_config/BUILD | 12 - tensorflow/core/platform/hadoop/BUILD | 12 - tensorflow/core/platform/s3/BUILD | 12 - tensorflow/core/profiler/BUILD | 15 - tensorflow/core/profiler/internal/BUILD | 14 - .../core/profiler/internal/advisor/BUILD | 15 - tensorflow/core/util/ctc/BUILD | 12 - tensorflow/core/util/tensor_bundle/BUILD | 15 - tensorflow/examples/adding_an_op/BUILD | 12 - tensorflow/examples/android/BUILD | 16 - tensorflow/examples/benchmark/BUILD | 6 - .../examples/get_started/regression/BUILD | 12 - .../examples/how_tos/reading_data/BUILD | 12 - tensorflow/examples/image_retraining/BUILD | 12 - tensorflow/examples/label_image/BUILD | 16 +- tensorflow/examples/learn/BUILD | 12 - tensorflow/examples/multibox_detector/BUILD | 14 - tensorflow/examples/saved_model/BUILD | 13 - tensorflow/examples/speech_commands/BUILD | 12 - .../examples/tutorials/estimators/BUILD | 12 - tensorflow/examples/tutorials/layers/BUILD | 12 - tensorflow/examples/tutorials/mnist/BUILD | 12 - tensorflow/examples/tutorials/monitors/BUILD | 12 - tensorflow/examples/tutorials/word2vec/BUILD | 11 - tensorflow/examples/wav_to_spectrogram/BUILD | 14 - tensorflow/java/BUILD | 12 - tensorflow/python/BUILD | 12 - tensorflow/python/data/BUILD | 12 - tensorflow/python/data/kernel_tests/BUILD | 12 - tensorflow/python/data/ops/BUILD | 12 - tensorflow/python/data/util/BUILD | 12 - tensorflow/python/debug/BUILD | 12 - tensorflow/python/eager/BUILD | 15 - tensorflow/python/estimator/BUILD | 12 - tensorflow/python/feature_column/BUILD | 12 - tensorflow/python/keras/BUILD | 12 - tensorflow/python/kernel_tests/BUILD | 12 - .../python/kernel_tests/distributions/BUILD | 12 - tensorflow/python/kernel_tests/linalg/BUILD | 12 - tensorflow/python/kernel_tests/random/BUILD | 12 - tensorflow/python/ops/distributions/BUILD | 12 - tensorflow/python/ops/linalg/BUILD | 12 - tensorflow/python/ops/losses/BUILD | 12 - tensorflow/python/profiler/BUILD | 15 - tensorflow/python/profiler/internal/BUILD | 15 - tensorflow/python/saved_model/BUILD | 12 - tensorflow/python/tools/BUILD | 14 - tensorflow/tools/api/generator/BUILD | 12 - tensorflow/tools/api/golden/BUILD | 12 - tensorflow/tools/api/lib/BUILD | 12 - tensorflow/tools/api/tests/BUILD | 12 - tensorflow/tools/benchmark/BUILD | 9 - tensorflow/tools/build_info/BUILD | 15 - tensorflow/tools/common/BUILD | 11 - tensorflow/tools/compatibility/BUILD | 15 - tensorflow/tools/dist_test/server/BUILD | 12 - tensorflow/tools/docker/BUILD | 12 - tensorflow/tools/docker/notebooks/BUILD | 12 - tensorflow/tools/docs/BUILD | 11 - tensorflow/tools/git/BUILD | 15 - tensorflow/tools/graph_transforms/BUILD | 11 - tensorflow/tools/mlpbtxt/BUILD | 12 - tensorflow/tools/proto_text/BUILD | 15 - tensorflow/tools/quantization/BUILD | 12 - tensorflow/tools/test/BUILD | 12 - tensorflow/user_ops/BUILD | 12 - third_party/hadoop/BUILD | 12 - third_party/mpi/BUILD | 12 - third_party/sycl/BUILD | 12 - third_party/sycl/sycl/BUILD | 12 - 272 files changed, 4 insertions(+), 3610 deletions(-) diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 6ab43638ba..0021b657d8 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -394,304 +394,6 @@ package_group( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - -filegroup( - name = "all_opensource_files", - data = [ - ":all_files", - "//tensorflow/c:all_files", - "//tensorflow/cc:all_files", - "//tensorflow/cc/saved_model:all_files", - "//tensorflow/cc/saved_model/python:all_files", - "//tensorflow/cc/tools:all_files", - "//tensorflow/compiler/aot:all_files", - "//tensorflow/compiler/aot/tests:all_files", - "//tensorflow/compiler/jit:all_files", - "//tensorflow/compiler/jit/graphcycles:all_files", - "//tensorflow/compiler/jit/kernels:all_files", - "//tensorflow/compiler/jit/legacy_flags:all_files", - "//tensorflow/compiler/jit/ops:all_files", - "//tensorflow/compiler/plugin:all_files", - "//tensorflow/compiler/tests:all_files", - "//tensorflow/compiler/tf2xla:all_files", - "//tensorflow/compiler/tf2xla/cc:all_files", - "//tensorflow/compiler/tf2xla/kernels:all_files", - "//tensorflow/compiler/tf2xla/lib:all_files", - "//tensorflow/compiler/tf2xla/ops:all_files", - "//tensorflow/compiler/xla:all_files", - "//tensorflow/compiler/xla/client:all_files", - "//tensorflow/compiler/xla/client/lib:all_files", - "//tensorflow/compiler/xla/client/xla_client:all_files", - "//tensorflow/compiler/xla/legacy_flags:all_files", - "//tensorflow/compiler/xla/python:all_files", - "//tensorflow/compiler/xla/service:all_files", - "//tensorflow/compiler/xla/service/cpu:all_files", - "//tensorflow/compiler/xla/service/gpu:all_files", - "//tensorflow/compiler/xla/service/gpu/llvm_gpu_backend:all_files", - "//tensorflow/compiler/xla/service/interpreter:all_files", - "//tensorflow/compiler/xla/service/llvm_ir:all_files", - "//tensorflow/compiler/xla/tests:all_files", - "//tensorflow/compiler/xla/tools:all_files", - "//tensorflow/compiler/xla/tools/parser:all_files", - "//tensorflow/contrib:all_files", - "//tensorflow/contrib/all_reduce:all_files", - "//tensorflow/contrib/android:all_files", - "//tensorflow/contrib/autograph:all_files", - "//tensorflow/contrib/autograph/converters:all_files", - "//tensorflow/contrib/autograph/impl:all_files", - "//tensorflow/contrib/autograph/pyct:all_files", - "//tensorflow/contrib/autograph/pyct/static_analysis:all_files", - "//tensorflow/contrib/autograph/utils:all_files", - "//tensorflow/contrib/batching:all_files", - "//tensorflow/contrib/bayesflow:all_files", - "//tensorflow/contrib/boosted_trees:all_files", - "//tensorflow/contrib/boosted_trees/estimator_batch:all_files", - "//tensorflow/contrib/boosted_trees/lib:all_files", - "//tensorflow/contrib/boosted_trees/proto:all_files", - "//tensorflow/contrib/boosted_trees/resources:all_files", - "//tensorflow/contrib/cloud:all_files", - "//tensorflow/contrib/cloud/kernels:all_files", - "//tensorflow/contrib/cluster_resolver:all_files", - "//tensorflow/contrib/coder:all_files", - "//tensorflow/contrib/compiler:all_files", - "//tensorflow/contrib/copy_graph:all_files", - "//tensorflow/contrib/crf:all_files", - "//tensorflow/contrib/cudnn_rnn:all_files", - "//tensorflow/contrib/data:all_files", - "//tensorflow/contrib/data/kernels:all_files", - "//tensorflow/contrib/data/python/kernel_tests:all_files", - "//tensorflow/contrib/data/python/ops:all_files", - "//tensorflow/contrib/decision_trees/proto:all_files", - "//tensorflow/contrib/deprecated:all_files", - "//tensorflow/contrib/distributions:all_files", - "//tensorflow/contrib/eager/proto:all_files", - "//tensorflow/contrib/eager/python:all_files", - "//tensorflow/contrib/estimator:all_files", - "//tensorflow/contrib/factorization:all_files", - "//tensorflow/contrib/factorization/examples:all_files", - "//tensorflow/contrib/factorization/kernels:all_files", - "//tensorflow/contrib/feature_column:all_files", - "//tensorflow/contrib/ffmpeg:all_files", - "//tensorflow/contrib/ffmpeg/default:all_files", - "//tensorflow/contrib/framework:all_files", - "//tensorflow/contrib/fused_conv:all_files", - "//tensorflow/contrib/gan:all_files", - "//tensorflow/contrib/gdr:all_files", - "//tensorflow/contrib/graph_editor:all_files", - "//tensorflow/contrib/grid_rnn:all_files", - "//tensorflow/contrib/hooks:all_files", - "//tensorflow/contrib/hvx/clock_cycle_profiling:all_files", - "//tensorflow/contrib/hvx/hvx_ops_support_checker:all_files", - "//tensorflow/contrib/image:all_files", - "//tensorflow/contrib/input_pipeline:all_files", - "//tensorflow/contrib/input_pipeline/kernels:all_files", - "//tensorflow/contrib/integrate:all_files", - "//tensorflow/contrib/keras:all_files", - "//tensorflow/contrib/kernel_methods:all_files", - "//tensorflow/contrib/kfac:all_files", - "//tensorflow/contrib/kfac/examples:all_files", - "//tensorflow/contrib/kfac/examples/tests:all_files", - "//tensorflow/contrib/kfac/python/kernel_tests:all_files", - "//tensorflow/contrib/kfac/python/ops:all_files", - "//tensorflow/contrib/labeled_tensor:all_files", - "//tensorflow/contrib/layers:all_files", - "//tensorflow/contrib/layers/kernels:all_files", - "//tensorflow/contrib/learn:all_files", - "//tensorflow/contrib/learn/python/learn/datasets:all_files", - "//tensorflow/contrib/legacy_seq2seq:all_files", - "//tensorflow/contrib/libsvm:all_files", - "//tensorflow/contrib/linalg:all_files", - "//tensorflow/contrib/linear_optimizer:all_files", - "//tensorflow/contrib/lite:all_files", - "//tensorflow/contrib/lite/java:all_files", - "//tensorflow/contrib/lite/java/demo/app/src/main:all_files", - "//tensorflow/contrib/lite/java/demo/app/src/main/assets:all_files", - "//tensorflow/contrib/lite/java/src/main/native:all_files", - "//tensorflow/contrib/lite/java/src/testhelper/java/org/tensorflow/lite:all_files", - "//tensorflow/contrib/lite/kernels:all_files", - "//tensorflow/contrib/lite/kernels/internal:all_files", - "//tensorflow/contrib/lite/models/smartreply:all_files", - "//tensorflow/contrib/lite/nnapi:all_files", - "//tensorflow/contrib/lite/python:all_files", - "//tensorflow/contrib/lite/schema:all_files", - "//tensorflow/contrib/lite/testing:all_files", - "//tensorflow/contrib/lite/toco:all_files", - "//tensorflow/contrib/lite/toco/graph_transformations/tests:all_files", - "//tensorflow/contrib/lite/toco/python:all_files", - "//tensorflow/contrib/lite/toco/tensorflow_graph_matching:all_files", - "//tensorflow/contrib/lite/toco/tflite:all_files", - "//tensorflow/contrib/lite/tools:all_files", - "//tensorflow/contrib/lookup:all_files", - "//tensorflow/contrib/losses:all_files", - "//tensorflow/contrib/makefile:all_files", - "//tensorflow/contrib/memory_stats:all_files", - "//tensorflow/contrib/meta_graph_transform:all_files", - "//tensorflow/contrib/metrics:all_files", - "//tensorflow/contrib/model_pruning:all_files", - "//tensorflow/contrib/model_pruning/examples/cifar10:all_files", - "//tensorflow/contrib/nccl:all_files", - "//tensorflow/contrib/nearest_neighbor:all_files", - "//tensorflow/contrib/nn:all_files", - "//tensorflow/contrib/opt:all_files", - "//tensorflow/contrib/periodic_resample:all_files", - "//tensorflow/contrib/predictor:all_files", - "//tensorflow/contrib/quantize:all_files", - "//tensorflow/contrib/receptive_field:all_files", - "//tensorflow/contrib/reduce_slice_ops:all_files", - "//tensorflow/contrib/remote_fused_graph/pylib:all_files", - "//tensorflow/contrib/resampler:all_files", - "//tensorflow/contrib/rnn:all_files", - "//tensorflow/contrib/saved_model:all_files", - "//tensorflow/contrib/saved_model/cc/saved_model:all_files", - "//tensorflow/contrib/seq2seq:all_files", - "//tensorflow/contrib/session_bundle:all_files", - "//tensorflow/contrib/session_bundle/example:all_files", - "//tensorflow/contrib/signal:all_files", - "//tensorflow/contrib/slim:all_files", - "//tensorflow/contrib/slim/python/slim/data:all_files", - "//tensorflow/contrib/slim/python/slim/nets:all_files", - "//tensorflow/contrib/solvers:all_files", - "//tensorflow/contrib/sparsemax:all_files", - "//tensorflow/contrib/specs:all_files", - "//tensorflow/contrib/staging:all_files", - "//tensorflow/contrib/stat_summarizer:all_files", - "//tensorflow/contrib/stateless:all_files", - "//tensorflow/contrib/summary:all_files", - "//tensorflow/contrib/tensor_forest:all_files", - "//tensorflow/contrib/tensor_forest/hybrid:all_files", - "//tensorflow/contrib/tensor_forest/kernels/v4:all_files", - "//tensorflow/contrib/tensor_forest/proto:all_files", - "//tensorflow/contrib/tensorboard:all_files", - "//tensorflow/contrib/tensorboard/db:all_files", - "//tensorflow/contrib/tensorrt:all_files", - "//tensorflow/contrib/testing:all_files", - "//tensorflow/contrib/text:all_files", - "//tensorflow/contrib/tfprof:all_files", - "//tensorflow/contrib/timeseries:all_files", - "//tensorflow/contrib/timeseries/examples:all_files", - "//tensorflow/contrib/timeseries/python/timeseries:all_files", - "//tensorflow/contrib/timeseries/python/timeseries/state_space_models:all_files", - "//tensorflow/contrib/tpu:all_files", - "//tensorflow/contrib/tpu/profiler:all_files", - "//tensorflow/contrib/tpu/proto:all_files", - "//tensorflow/contrib/training:all_files", - "//tensorflow/contrib/util:all_files", - "//tensorflow/contrib/verbs:all_files", - "//tensorflow/core:all_files", - "//tensorflow/core/api_def:all_files", - "//tensorflow/core/common_runtime/eager:all_files", - "//tensorflow/core/debug:all_files", - "//tensorflow/core/distributed_runtime:all_files", - "//tensorflow/core/distributed_runtime/rpc:all_files", - "//tensorflow/core/grappler:all_files", - "//tensorflow/core/grappler/clusters:all_files", - "//tensorflow/core/grappler/costs:all_files", - "//tensorflow/core/grappler/inputs:all_files", - "//tensorflow/core/grappler/optimizers:all_files", - "//tensorflow/core/grappler/utils:all_files", - "//tensorflow/core/kernels:all_files", - "//tensorflow/core/kernels/batching_util:all_files", - "//tensorflow/core/kernels/data:all_files", - "//tensorflow/core/kernels/data/sql:all_files", - "//tensorflow/core/kernels/fuzzing:all_files", - "//tensorflow/core/kernels/hexagon:all_files", - "//tensorflow/core/kernels/neon:all_files", - "//tensorflow/core/lib/db:all_files", - "//tensorflow/core/ops/compat:all_files", - "//tensorflow/core/platform/cloud:all_files", - "//tensorflow/core/platform/default/build_config:all_files", - "//tensorflow/core/platform/hadoop:all_files", - "//tensorflow/core/platform/s3:all_files", - "//tensorflow/core/profiler:all_files", - "//tensorflow/core/profiler/internal:all_files", - "//tensorflow/core/profiler/internal/advisor:all_files", - "//tensorflow/core/util/ctc:all_files", - "//tensorflow/core/util/tensor_bundle:all_files", - "//tensorflow/examples/adding_an_op:all_files", - "//tensorflow/examples/android:all_files", - "//tensorflow/examples/benchmark:all_files", - "//tensorflow/examples/get_started/regression:all_files", - "//tensorflow/examples/how_tos/reading_data:all_files", - "//tensorflow/examples/image_retraining:all_files", - "//tensorflow/examples/label_image:all_files", - "//tensorflow/examples/learn:all_files", - "//tensorflow/examples/multibox_detector:all_files", - "//tensorflow/examples/saved_model:all_files", - "//tensorflow/examples/speech_commands:all_files", - "//tensorflow/examples/tutorials/estimators:all_files", - "//tensorflow/examples/tutorials/layers:all_files", - "//tensorflow/examples/tutorials/mnist:all_files", - "//tensorflow/examples/tutorials/monitors:all_files", - "//tensorflow/examples/tutorials/word2vec:all_files", - "//tensorflow/examples/wav_to_spectrogram:all_files", - "//tensorflow/go:all_files", - "//tensorflow/java:all_files", - "//tensorflow/java/src/main/java/org/tensorflow/examples:all_files", - "//tensorflow/java/src/main/native:all_files", - "//tensorflow/python:all_files", - "//tensorflow/python/data:all_files", - "//tensorflow/python/data/kernel_tests:all_files", - "//tensorflow/python/data/ops:all_files", - "//tensorflow/python/data/util:all_files", - "//tensorflow/python/debug:all_files", - "//tensorflow/python/eager:all_files", - "//tensorflow/python/estimator:all_files", - "//tensorflow/python/feature_column:all_files", - "//tensorflow/python/keras:all_files", - "//tensorflow/python/kernel_tests:all_files", - "//tensorflow/python/kernel_tests/distributions:all_files", - "//tensorflow/python/kernel_tests/linalg:all_files", - "//tensorflow/python/kernel_tests/random:all_files", - "//tensorflow/python/kernel_tests/testdata:all_files", - "//tensorflow/python/ops/distributions:all_files", - "//tensorflow/python/ops/linalg:all_files", - "//tensorflow/python/ops/losses:all_files", - "//tensorflow/python/profiler:all_files", - "//tensorflow/python/profiler/internal:all_files", - "//tensorflow/python/saved_model:all_files", - "//tensorflow/python/tools:all_files", - "//tensorflow/tools/api/generator:all_files", - "//tensorflow/tools/api/golden:all_files", - "//tensorflow/tools/api/lib:all_files", - "//tensorflow/tools/api/tests:all_files", - "//tensorflow/tools/benchmark:all_files", - "//tensorflow/tools/build_info:all_files", - "//tensorflow/tools/ci_build/gpu_build:all_files", - "//tensorflow/tools/common:all_files", - "//tensorflow/tools/compatibility:all_files", - "//tensorflow/tools/dist_test/server:all_files", - "//tensorflow/tools/docker:all_files", - "//tensorflow/tools/docker/notebooks:all_files", - "//tensorflow/tools/docs:all_files", - "//tensorflow/tools/git:all_files", - "//tensorflow/tools/graph_transforms:all_files", - "//tensorflow/tools/mlpbtxt:all_files", - "//tensorflow/tools/proto_text:all_files", - "//tensorflow/tools/quantization:all_files", - "//tensorflow/tools/test:all_files", - "//tensorflow/user_ops:all_files", - "//third_party/eigen3:all_files", - "//third_party/fft2d:all_files", - "//third_party/flatbuffers:all_files", - "//third_party/hadoop:all_files", - "//third_party/sycl:all_files", - "//third_party/sycl/sycl:all_files", - ], - visibility = ["//visibility:public"], -) - load( "//third_party/mkl:build_defs.bzl", "if_mkl", diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index 249135f728..2367014cd0 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -287,18 +287,3 @@ tf_cuda_library( "//tensorflow/python:cpp_shape_inference_proto_cc", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/cc/BUILD b/tensorflow/cc/BUILD index 9060c19e9d..079e063d3e 100644 --- a/tensorflow/cc/BUILD +++ b/tensorflow/cc/BUILD @@ -620,18 +620,6 @@ tf_cc_binary( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "queue_runner", srcs = ["training/queue_runner.cc"], diff --git a/tensorflow/cc/saved_model/BUILD b/tensorflow/cc/saved_model/BUILD index d29ad3ebcb..06a3be18e0 100644 --- a/tensorflow/cc/saved_model/BUILD +++ b/tensorflow/cc/saved_model/BUILD @@ -94,18 +94,3 @@ filegroup( "testdata/half_plus_two/**", ]), ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/cc/saved_model/python/BUILD b/tensorflow/cc/saved_model/python/BUILD index f5fbc75edc..6f04ebdc55 100644 --- a/tensorflow/cc/saved_model/python/BUILD +++ b/tensorflow/cc/saved_model/python/BUILD @@ -7,18 +7,6 @@ package( default_visibility = ["//visibility:public"], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - load("//tensorflow/core:platform/default/build_config.bzl", "tf_py_clif_cc") tf_py_clif_cc( diff --git a/tensorflow/cc/tools/BUILD b/tensorflow/cc/tools/BUILD index f413a5cc52..6f1c873540 100644 --- a/tensorflow/cc/tools/BUILD +++ b/tensorflow/cc/tools/BUILD @@ -41,18 +41,3 @@ tf_cc_test( "//tensorflow/core:testlib", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/aot/BUILD b/tensorflow/compiler/aot/BUILD index ffa2d08829..fa03b1f3c2 100644 --- a/tensorflow/compiler/aot/BUILD +++ b/tensorflow/compiler/aot/BUILD @@ -250,17 +250,3 @@ exports_files([ "benchmark_main.template", # used by tf_library(...,gen_benchmark=True) "test.cc", # used by tf_library(...,gen_test=True) ]) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/aot/tests/BUILD b/tensorflow/compiler/aot/tests/BUILD index 28aab6eb61..b053dad1b5 100644 --- a/tensorflow/compiler/aot/tests/BUILD +++ b/tensorflow/compiler/aot/tests/BUILD @@ -182,17 +182,3 @@ tf_cc_test( "//third_party/eigen3", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index 8e505da622..9ea246ffdc 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -365,20 +365,6 @@ tf_cc_test( ], ) -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # This target can be used by XLA device plugins to prevent circular dependencies, and provides access to all of the required headers for building a device library. cc_header_only_library( name = "xla_jit_headers_lib", diff --git a/tensorflow/compiler/jit/graphcycles/BUILD b/tensorflow/compiler/jit/graphcycles/BUILD index 15507b3851..676f71a75a 100644 --- a/tensorflow/compiler/jit/graphcycles/BUILD +++ b/tensorflow/compiler/jit/graphcycles/BUILD @@ -27,17 +27,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/jit/kernels/BUILD b/tensorflow/compiler/jit/kernels/BUILD index 616a7f8f15..00a6f4075f 100644 --- a/tensorflow/compiler/jit/kernels/BUILD +++ b/tensorflow/compiler/jit/kernels/BUILD @@ -41,17 +41,3 @@ cc_library( ], alwayslink = 1, ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/jit/legacy_flags/BUILD b/tensorflow/compiler/jit/legacy_flags/BUILD index 9cd66fc13c..5d211f4d73 100644 --- a/tensorflow/compiler/jit/legacy_flags/BUILD +++ b/tensorflow/compiler/jit/legacy_flags/BUILD @@ -63,17 +63,3 @@ cc_library( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/jit/ops/BUILD b/tensorflow/compiler/jit/ops/BUILD index e5787ca4c8..c9e46bc147 100644 --- a/tensorflow/compiler/jit/ops/BUILD +++ b/tensorflow/compiler/jit/ops/BUILD @@ -17,17 +17,3 @@ cc_library( deps = ["//tensorflow/core:framework"], alwayslink = 1, ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/plugin/BUILD b/tensorflow/compiler/plugin/BUILD index da4bc44c7a..238fd15166 100644 --- a/tensorflow/compiler/plugin/BUILD +++ b/tensorflow/compiler/plugin/BUILD @@ -49,17 +49,3 @@ cc_library( "//tensorflow/compiler/jit:xla_device", ], ) - -#----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD index 1c5a8f8e69..edabdc218a 100644 --- a/tensorflow/compiler/tests/BUILD +++ b/tensorflow/compiler/tests/BUILD @@ -835,17 +835,3 @@ tf_xla_py_test( "//tensorflow/python:platform_test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD index eb20ca501c..8c33bf179c 100644 --- a/tensorflow/compiler/tf2xla/BUILD +++ b/tensorflow/compiler/tf2xla/BUILD @@ -462,17 +462,3 @@ cc_library( "//tensorflow/core:protos_all_cc", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tf2xla/cc/BUILD b/tensorflow/compiler/tf2xla/cc/BUILD index 311dddca94..c30bb9cacd 100644 --- a/tensorflow/compiler/tf2xla/cc/BUILD +++ b/tensorflow/compiler/tf2xla/cc/BUILD @@ -51,17 +51,3 @@ cc_library( "//tensorflow/core:protos_all_cc", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD index 0bbfe86de3..f1bc7d6af4 100644 --- a/tensorflow/compiler/tf2xla/kernels/BUILD +++ b/tensorflow/compiler/tf2xla/kernels/BUILD @@ -217,17 +217,3 @@ cc_library( ], alwayslink = 1, ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tf2xla/lib/BUILD b/tensorflow/compiler/tf2xla/lib/BUILD index 488fda74bf..344773c8c5 100644 --- a/tensorflow/compiler/tf2xla/lib/BUILD +++ b/tensorflow/compiler/tf2xla/lib/BUILD @@ -140,17 +140,3 @@ cc_library( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/tf2xla/ops/BUILD b/tensorflow/compiler/tf2xla/ops/BUILD index 98f72b3792..aeb743a663 100644 --- a/tensorflow/compiler/tf2xla/ops/BUILD +++ b/tensorflow/compiler/tf2xla/ops/BUILD @@ -39,17 +39,3 @@ tf_gen_op_wrapper_py( ":sendrecv_ops", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD index cd13db4d30..751777222f 100644 --- a/tensorflow/compiler/xla/BUILD +++ b/tensorflow/compiler/xla/BUILD @@ -654,18 +654,6 @@ tf_cc_test( # ----------------------------------------------------------------------------- -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # This is a headers target that extra XLA devices can use to prevent circular dependencies. Devices that are compiled as separate shared objects can also use it to prevent linking of library code. cc_header_only_library( name = "xla_headers_lib", diff --git a/tensorflow/compiler/xla/client/BUILD b/tensorflow/compiler/xla/client/BUILD index 5094e5ce67..a299c2afd4 100644 --- a/tensorflow/compiler/xla/client/BUILD +++ b/tensorflow/compiler/xla/client/BUILD @@ -214,17 +214,3 @@ cc_library( "//tensorflow/compiler/xla:xla_data_proto", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD index fca2bf2688..d02972f2c0 100644 --- a/tensorflow/compiler/xla/client/lib/BUILD +++ b/tensorflow/compiler/xla/client/lib/BUILD @@ -48,17 +48,3 @@ cc_library( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/client/xla_client/BUILD b/tensorflow/compiler/xla/client/xla_client/BUILD index 60f13e04cb..b1dba16856 100644 --- a/tensorflow/compiler/xla/client/xla_client/BUILD +++ b/tensorflow/compiler/xla/client/xla_client/BUILD @@ -76,17 +76,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/legacy_flags/BUILD b/tensorflow/compiler/xla/legacy_flags/BUILD index 0a9725db0a..89353448e2 100644 --- a/tensorflow/compiler/xla/legacy_flags/BUILD +++ b/tensorflow/compiler/xla/legacy_flags/BUILD @@ -75,17 +75,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/python/BUILD b/tensorflow/compiler/xla/python/BUILD index e2972f0601..0517a5502e 100644 --- a/tensorflow/compiler/xla/python/BUILD +++ b/tensorflow/compiler/xla/python/BUILD @@ -72,15 +72,3 @@ tf_py_wrap_cc( "//tensorflow/compiler/xla/service:cpu_plugin", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index bde749d317..b7d1bf64d0 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -2651,17 +2651,3 @@ cc_library( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD index 0faa9e9c41..966e2d0fc5 100644 --- a/tensorflow/compiler/xla/service/cpu/BUILD +++ b/tensorflow/compiler/xla/service/cpu/BUILD @@ -916,17 +916,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 93b2f2a474..f1707442fe 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -700,17 +700,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/BUILD b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/BUILD index f4c4dcdafd..86c4ac18b0 100644 --- a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/BUILD +++ b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/BUILD @@ -68,17 +68,3 @@ tf_cc_test( "@llvm//:support", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/service/interpreter/BUILD b/tensorflow/compiler/xla/service/interpreter/BUILD index 0db3863f24..4550548495 100644 --- a/tensorflow/compiler/xla/service/interpreter/BUILD +++ b/tensorflow/compiler/xla/service/interpreter/BUILD @@ -120,14 +120,3 @@ cc_library( "//tensorflow/core:stream_executor_no_cuda", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/compiler/xla/service/llvm_ir/BUILD b/tensorflow/compiler/xla/service/llvm_ir/BUILD index 37261ed1e6..f1e7fc2953 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/BUILD +++ b/tensorflow/compiler/xla/service/llvm_ir/BUILD @@ -169,17 +169,3 @@ cc_library( "@llvm//:core", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 2fd97fa38e..e337669aeb 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -1960,17 +1960,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/tools/BUILD b/tensorflow/compiler/xla/tools/BUILD index 2e55f609d1..0bc4045a54 100644 --- a/tensorflow/compiler/xla/tools/BUILD +++ b/tensorflow/compiler/xla/tools/BUILD @@ -223,17 +223,3 @@ tf_cc_binary( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/compiler/xla/tools/parser/BUILD b/tensorflow/compiler/xla/tools/parser/BUILD index 97aacf6b39..0fa4b98d0a 100644 --- a/tensorflow/compiler/xla/tools/parser/BUILD +++ b/tensorflow/compiler/xla/tools/parser/BUILD @@ -70,17 +70,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -# ----------------------------------------------------------------------------- - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index bdbd738906..1ca70e7122 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -159,15 +159,3 @@ cc_library( "//tensorflow/contrib/tpu:all_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/all_reduce/BUILD b/tensorflow/contrib/all_reduce/BUILD index 8dff93b4f8..62d1b1cf07 100644 --- a/tensorflow/contrib/all_reduce/BUILD +++ b/tensorflow/contrib/all_reduce/BUILD @@ -45,16 +45,3 @@ tf_py_test( "//tensorflow/python:state_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/android/BUILD b/tensorflow/contrib/android/BUILD index 4bff3c27d2..60306ebdc6 100644 --- a/tensorflow/contrib/android/BUILD +++ b/tensorflow/contrib/android/BUILD @@ -38,20 +38,6 @@ cc_library( alwayslink = 1, ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # JAR with Java bindings to TF. android_library( name = "android_tensorflow_inference_java", diff --git a/tensorflow/contrib/batching/BUILD b/tensorflow/contrib/batching/BUILD index ee67909133..d65c990c87 100644 --- a/tensorflow/contrib/batching/BUILD +++ b/tensorflow/contrib/batching/BUILD @@ -112,14 +112,3 @@ py_test( "//tensorflow/python:script_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/batching/test_util/BUILD b/tensorflow/contrib/batching/test_util/BUILD index 6db627faad..7cb2d8079b 100644 --- a/tensorflow/contrib/batching/test_util/BUILD +++ b/tensorflow/contrib/batching/test_util/BUILD @@ -8,17 +8,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) - cc_library( name = "fake_clock_env", testonly = 1, diff --git a/tensorflow/contrib/batching/util/BUILD b/tensorflow/contrib/batching/util/BUILD index 2a84a7712a..8f81b6702f 100644 --- a/tensorflow/contrib/batching/util/BUILD +++ b/tensorflow/contrib/batching/util/BUILD @@ -8,18 +8,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "**/google_*", - ], - ), -) - cc_library( name = "periodic_function_dynamic", hdrs = ["periodic_function.h"], diff --git a/tensorflow/contrib/bayesflow/BUILD b/tensorflow/contrib/bayesflow/BUILD index a55029b314..5a2d7f6a3c 100644 --- a/tensorflow/contrib/bayesflow/BUILD +++ b/tensorflow/contrib/bayesflow/BUILD @@ -57,15 +57,3 @@ cuda_py_test( "//tensorflow/python:random_seed", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/boosted_trees/BUILD b/tensorflow/contrib/boosted_trees/BUILD index 6fdcd0f996..ddeda0079c 100644 --- a/tensorflow/contrib/boosted_trees/BUILD +++ b/tensorflow/contrib/boosted_trees/BUILD @@ -14,15 +14,6 @@ load("//tensorflow:tensorflow.bzl", "tf_gen_op_wrapper_py") load("//tensorflow:tensorflow.bzl", "tf_kernel_library") load("//tensorflow:tensorflow.bzl", "tf_custom_op_py_library") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = ["**/OWNERS"], - ), - visibility = ["//tensorflow:__subpackages__"], -) - package_group(name = "friends") cc_library( diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD index dcd235f876..17e20c4b31 100644 --- a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD +++ b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD @@ -10,15 +10,6 @@ package( load("//tensorflow:tensorflow.bzl", "py_test") -filegroup( - name = "all_files", - srcs = glob( - include = ["**/*"], - exclude = ["**/OWNERS"], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "init_py", srcs = ["__init__.py"], diff --git a/tensorflow/contrib/boosted_trees/lib/BUILD b/tensorflow/contrib/boosted_trees/lib/BUILD index 131bd48562..3028c22817 100644 --- a/tensorflow/contrib/boosted_trees/lib/BUILD +++ b/tensorflow/contrib/boosted_trees/lib/BUILD @@ -15,17 +15,6 @@ load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow:tensorflow.bzl", "tf_cc_test") load("//tensorflow:tensorflow.bzl", "tf_cc_binary") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # Utils cc_library( diff --git a/tensorflow/contrib/boosted_trees/proto/BUILD b/tensorflow/contrib/boosted_trees/proto/BUILD index 9a61e163eb..b07f0a4314 100644 --- a/tensorflow/contrib/boosted_trees/proto/BUILD +++ b/tensorflow/contrib/boosted_trees/proto/BUILD @@ -4,17 +4,6 @@ exports_files(["LICENSE"]) load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "learner_proto", srcs = [ diff --git a/tensorflow/contrib/boosted_trees/resources/BUILD b/tensorflow/contrib/boosted_trees/resources/BUILD index 9fc101612f..c065186845 100644 --- a/tensorflow/contrib/boosted_trees/resources/BUILD +++ b/tensorflow/contrib/boosted_trees/resources/BUILD @@ -9,17 +9,6 @@ package( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "stamped_resource", hdrs = ["stamped_resource.h"], diff --git a/tensorflow/contrib/cloud/BUILD b/tensorflow/contrib/cloud/BUILD index fe8bd072af..f3a75e8688 100644 --- a/tensorflow/contrib/cloud/BUILD +++ b/tensorflow/contrib/cloud/BUILD @@ -14,18 +14,6 @@ load( "tf_py_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_gen_op_libs( op_lib_names = ["bigquery_reader_ops"], deps = [ diff --git a/tensorflow/contrib/cloud/kernels/BUILD b/tensorflow/contrib/cloud/kernels/BUILD index d5fc604de9..ff46f0daa8 100644 --- a/tensorflow/contrib/cloud/kernels/BUILD +++ b/tensorflow/contrib/cloud/kernels/BUILD @@ -20,20 +20,6 @@ load( "tf_proto_library", ) -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_kernel_library( name = "bigquery_reader_ops", srcs = ["bigquery_reader_ops.cc"], diff --git a/tensorflow/contrib/cluster_resolver/BUILD b/tensorflow/contrib/cluster_resolver/BUILD index 1a124eca36..c239e6f8f9 100644 --- a/tensorflow/contrib/cluster_resolver/BUILD +++ b/tensorflow/contrib/cluster_resolver/BUILD @@ -10,19 +10,6 @@ package( licenses(["notice"]) # Apache 2.0 -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) - py_library( name = "cluster_resolver_pip", srcs = [ diff --git a/tensorflow/contrib/coder/BUILD b/tensorflow/contrib/coder/BUILD index ec3d550b70..ce12e38248 100644 --- a/tensorflow/contrib/coder/BUILD +++ b/tensorflow/contrib/coder/BUILD @@ -154,14 +154,3 @@ tf_py_test( ], main = "python/ops/coder_ops_test.py", ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/compiler/BUILD b/tensorflow/contrib/compiler/BUILD index 388d8e6ed6..bcee0b04c8 100644 --- a/tensorflow/contrib/compiler/BUILD +++ b/tensorflow/contrib/compiler/BUILD @@ -46,15 +46,3 @@ cuda_py_test( ], xla_enabled = True, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/copy_graph/BUILD b/tensorflow/contrib/copy_graph/BUILD index 8ec706df74..fa44c4d54e 100644 --- a/tensorflow/contrib/copy_graph/BUILD +++ b/tensorflow/contrib/copy_graph/BUILD @@ -41,15 +41,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/crf/BUILD b/tensorflow/contrib/crf/BUILD index 7aad4abdb9..5c1a17df4f 100644 --- a/tensorflow/contrib/crf/BUILD +++ b/tensorflow/contrib/crf/BUILD @@ -40,15 +40,3 @@ cuda_py_tests( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/cudnn_rnn/BUILD b/tensorflow/contrib/cudnn_rnn/BUILD index fa86ad38c9..8b5d13f725 100644 --- a/tensorflow/contrib/cudnn_rnn/BUILD +++ b/tensorflow/contrib/cudnn_rnn/BUILD @@ -123,15 +123,3 @@ cuda_py_test( "requires_cudnn5", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/data/BUILD b/tensorflow/contrib/data/BUILD index 9e25a77d9f..35312f06b3 100644 --- a/tensorflow/contrib/data/BUILD +++ b/tensorflow/contrib/data/BUILD @@ -44,17 +44,3 @@ tf_custom_op_library( tf_gen_op_libs( op_lib_names = ["dataset_ops"], ) - -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/data/kernels/BUILD b/tensorflow/contrib/data/kernels/BUILD index c87da7dfaa..83ada6fb67 100644 --- a/tensorflow/contrib/data/kernels/BUILD +++ b/tensorflow/contrib/data/kernels/BUILD @@ -61,14 +61,3 @@ cc_library( "@protobuf_archive//:protobuf_headers", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/data/python/kernel_tests/BUILD b/tensorflow/contrib/data/python/kernel_tests/BUILD index 0b3bf63f79..0f4c9e48cf 100644 --- a/tensorflow/contrib/data/python/kernel_tests/BUILD +++ b/tensorflow/contrib/data/python/kernel_tests/BUILD @@ -513,17 +513,3 @@ tf_py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/data/python/ops/BUILD b/tensorflow/contrib/data/python/ops/BUILD index 647620eb84..236792bb98 100644 --- a/tensorflow/contrib/data/python/ops/BUILD +++ b/tensorflow/contrib/data/python/ops/BUILD @@ -183,15 +183,3 @@ py_library( "//tensorflow/python/data/util:sparse", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/decision_trees/proto/BUILD b/tensorflow/contrib/decision_trees/proto/BUILD index ae3847b8b6..3b50a48336 100644 --- a/tensorflow/contrib/decision_trees/proto/BUILD +++ b/tensorflow/contrib/decision_trees/proto/BUILD @@ -13,14 +13,6 @@ load( "tf_pyclif_proto_library", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "generic_tree_model", srcs = ["generic_tree_model.proto"], diff --git a/tensorflow/contrib/deprecated/BUILD b/tensorflow/contrib/deprecated/BUILD index 3dfbbf5527..401527f1e7 100644 --- a/tensorflow/contrib/deprecated/BUILD +++ b/tensorflow/contrib/deprecated/BUILD @@ -30,15 +30,3 @@ py_test( "//tensorflow/python:logging_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/distributions/BUILD b/tensorflow/contrib/distributions/BUILD index 682448b84b..231abaa2f3 100644 --- a/tensorflow/contrib/distributions/BUILD +++ b/tensorflow/contrib/distributions/BUILD @@ -746,18 +746,6 @@ cuda_py_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # === Bijector Tests ========================================================== cuda_py_test( diff --git a/tensorflow/contrib/eager/proto/BUILD b/tensorflow/contrib/eager/proto/BUILD index aedfec8924..b016d2dcb5 100644 --- a/tensorflow/contrib/eager/proto/BUILD +++ b/tensorflow/contrib/eager/proto/BUILD @@ -4,17 +4,6 @@ exports_files(["LICENSE"]) load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "checkpointable_object_graph_proto", srcs = [ diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD index 4fba014d6f..7a8c11e3bb 100644 --- a/tensorflow/contrib/eager/python/BUILD +++ b/tensorflow/contrib/eager/python/BUILD @@ -272,16 +272,3 @@ cuda_py_test( ], tags = ["notsan"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index c846343d6d..d125e40f6c 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -9,18 +9,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "estimator_py", srcs = ["__init__.py"], diff --git a/tensorflow/contrib/factorization/BUILD b/tensorflow/contrib/factorization/BUILD index ad8568ad44..0a648d5d40 100644 --- a/tensorflow/contrib/factorization/BUILD +++ b/tensorflow/contrib/factorization/BUILD @@ -347,16 +347,3 @@ cuda_py_test( ], main = "python/kernel_tests/masked_matmul_benchmark.py", ) - -# All files -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/factorization/examples/BUILD b/tensorflow/contrib/factorization/examples/BUILD index bbe842bd5c..363baa121a 100644 --- a/tensorflow/contrib/factorization/examples/BUILD +++ b/tensorflow/contrib/factorization/examples/BUILD @@ -21,14 +21,3 @@ tf_py_test( ], tags = ["notsan"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/factorization/kernels/BUILD b/tensorflow/contrib/factorization/kernels/BUILD index 44eab56011..ea8b9a17a2 100644 --- a/tensorflow/contrib/factorization/kernels/BUILD +++ b/tensorflow/contrib/factorization/kernels/BUILD @@ -67,14 +67,3 @@ tf_cc_test( "//tensorflow/core:testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/feature_column/BUILD b/tensorflow/contrib/feature_column/BUILD index 3614b2b15a..aab7d0c9e8 100644 --- a/tensorflow/contrib/feature_column/BUILD +++ b/tensorflow/contrib/feature_column/BUILD @@ -8,18 +8,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "py_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "feature_column_py", srcs = ["__init__.py"], diff --git a/tensorflow/contrib/ffmpeg/BUILD b/tensorflow/contrib/ffmpeg/BUILD index eccce99071..f7b3273a4d 100644 --- a/tensorflow/contrib/ffmpeg/BUILD +++ b/tensorflow/contrib/ffmpeg/BUILD @@ -180,15 +180,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/ffmpeg/default/BUILD b/tensorflow/contrib/ffmpeg/default/BUILD index 6b455567d7..59bad8982d 100644 --- a/tensorflow/contrib/ffmpeg/default/BUILD +++ b/tensorflow/contrib/ffmpeg/default/BUILD @@ -74,15 +74,3 @@ tf_cc_test( "//tensorflow/core:test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/framework/BUILD b/tensorflow/contrib/framework/BUILD index ac043fda06..b1c8ad49ea 100644 --- a/tensorflow/contrib/framework/BUILD +++ b/tensorflow/contrib/framework/BUILD @@ -321,15 +321,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/fused_conv/BUILD b/tensorflow/contrib/fused_conv/BUILD index ce37672895..0eb6889db1 100644 --- a/tensorflow/contrib/fused_conv/BUILD +++ b/tensorflow/contrib/fused_conv/BUILD @@ -157,15 +157,3 @@ cuda_py_test( "requires_cudnn6", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/gan/BUILD b/tensorflow/contrib/gan/BUILD index 0eb0e3cbe2..9e56d3c039 100644 --- a/tensorflow/contrib/gan/BUILD +++ b/tensorflow/contrib/gan/BUILD @@ -544,15 +544,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/gdr/BUILD b/tensorflow/contrib/gdr/BUILD index 707ae25d48..e534fdc177 100644 --- a/tensorflow/contrib/gdr/BUILD +++ b/tensorflow/contrib/gdr/BUILD @@ -9,18 +9,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "c_srcs", data = glob([ diff --git a/tensorflow/contrib/graph_editor/BUILD b/tensorflow/contrib/graph_editor/BUILD index 967ad2fc09..1711100e3a 100644 --- a/tensorflow/contrib/graph_editor/BUILD +++ b/tensorflow/contrib/graph_editor/BUILD @@ -39,18 +39,6 @@ py_library( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "match", srcs = ["tests/match.py"], diff --git a/tensorflow/contrib/grid_rnn/BUILD b/tensorflow/contrib/grid_rnn/BUILD index d601a1ec6f..d0b4464066 100644 --- a/tensorflow/contrib/grid_rnn/BUILD +++ b/tensorflow/contrib/grid_rnn/BUILD @@ -41,15 +41,3 @@ cuda_py_tests( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/hooks/BUILD b/tensorflow/contrib/hooks/BUILD index 1b528d7afc..d65b2d6026 100644 --- a/tensorflow/contrib/hooks/BUILD +++ b/tensorflow/contrib/hooks/BUILD @@ -23,14 +23,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/hvx/clock_cycle_profiling/BUILD b/tensorflow/contrib/hvx/clock_cycle_profiling/BUILD index 324035100d..e39c60b252 100644 --- a/tensorflow/contrib/hvx/clock_cycle_profiling/BUILD +++ b/tensorflow/contrib/hvx/clock_cycle_profiling/BUILD @@ -13,18 +13,6 @@ exports_files(["LICENSE"]) package(default_visibility = ["//visibility:public"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_cc_binary( name = "clock_cycle_profiling", testonly = 1, diff --git a/tensorflow/contrib/hvx/hvx_ops_support_checker/BUILD b/tensorflow/contrib/hvx/hvx_ops_support_checker/BUILD index 909dc396a3..0081fb6177 100644 --- a/tensorflow/contrib/hvx/hvx_ops_support_checker/BUILD +++ b/tensorflow/contrib/hvx/hvx_ops_support_checker/BUILD @@ -10,17 +10,6 @@ exports_files(["LICENSE"]) load("//tensorflow:tensorflow.bzl", "tf_cc_binary") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) - tf_cc_binary( name = "hvx_ops_support_checker", testonly = 1, diff --git a/tensorflow/contrib/image/BUILD b/tensorflow/contrib/image/BUILD index 79eb3762ed..da450480b3 100755 --- a/tensorflow/contrib/image/BUILD +++ b/tensorflow/contrib/image/BUILD @@ -384,15 +384,3 @@ cuda_py_test( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/input_pipeline/BUILD b/tensorflow/contrib/input_pipeline/BUILD index 9d6b4d5d87..0e34315db4 100644 --- a/tensorflow/contrib/input_pipeline/BUILD +++ b/tensorflow/contrib/input_pipeline/BUILD @@ -114,14 +114,3 @@ tf_cc_tests( "//tensorflow/core:testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/input_pipeline/kernels/BUILD b/tensorflow/contrib/input_pipeline/kernels/BUILD index f20a6e38d4..797605b8fe 100644 --- a/tensorflow/contrib/input_pipeline/kernels/BUILD +++ b/tensorflow/contrib/input_pipeline/kernels/BUILD @@ -17,14 +17,3 @@ cc_library( ], alwayslink = 1, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/integrate/BUILD b/tensorflow/contrib/integrate/BUILD index 66948c1ea1..0b7d64f4ed 100644 --- a/tensorflow/contrib/integrate/BUILD +++ b/tensorflow/contrib/integrate/BUILD @@ -42,14 +42,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/kafka/BUILD b/tensorflow/contrib/kafka/BUILD index 1c3974871c..3913c9dc7a 100644 --- a/tensorflow/contrib/kafka/BUILD +++ b/tensorflow/contrib/kafka/BUILD @@ -119,17 +119,3 @@ tf_py_test( "notap", ], ) - -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/keras/BUILD b/tensorflow/contrib/keras/BUILD index 7e0019ce4a..7a4cab20d1 100644 --- a/tensorflow/contrib/keras/BUILD +++ b/tensorflow/contrib/keras/BUILD @@ -52,15 +52,3 @@ py_library( "//tensorflow/python/keras", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kernel_methods/BUILD b/tensorflow/contrib/kernel_methods/BUILD index eff7dfeb4c..87c2dcd89b 100644 --- a/tensorflow/contrib/kernel_methods/BUILD +++ b/tensorflow/contrib/kernel_methods/BUILD @@ -90,15 +90,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kfac/BUILD b/tensorflow/contrib/kfac/BUILD index 9a5759bf14..b719046b37 100644 --- a/tensorflow/contrib/kfac/BUILD +++ b/tensorflow/contrib/kfac/BUILD @@ -24,15 +24,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kfac/examples/BUILD b/tensorflow/contrib/kfac/examples/BUILD index 89965eda37..7dd40c19c5 100644 --- a/tensorflow/contrib/kfac/examples/BUILD +++ b/tensorflow/contrib/kfac/examples/BUILD @@ -58,15 +58,3 @@ py_library( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kfac/examples/tests/BUILD b/tensorflow/contrib/kfac/examples/tests/BUILD index ce7da95c12..ede7f183fe 100644 --- a/tensorflow/contrib/kfac/examples/tests/BUILD +++ b/tensorflow/contrib/kfac/examples/tests/BUILD @@ -50,15 +50,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kfac/python/kernel_tests/BUILD b/tensorflow/contrib/kfac/python/kernel_tests/BUILD index 146ae8b7e2..f73c24f8fb 100644 --- a/tensorflow/contrib/kfac/python/kernel_tests/BUILD +++ b/tensorflow/contrib/kfac/python/kernel_tests/BUILD @@ -155,15 +155,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/kfac/python/ops/BUILD b/tensorflow/contrib/kfac/python/ops/BUILD index d721ad08af..b897fd68a0 100644 --- a/tensorflow/contrib/kfac/python/ops/BUILD +++ b/tensorflow/contrib/kfac/python/ops/BUILD @@ -244,15 +244,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/labeled_tensor/BUILD b/tensorflow/contrib/labeled_tensor/BUILD index 894e6f6946..18b265ae80 100644 --- a/tensorflow/contrib/labeled_tensor/BUILD +++ b/tensorflow/contrib/labeled_tensor/BUILD @@ -213,14 +213,3 @@ py_test( "//tensorflow/python:math_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/layers/BUILD b/tensorflow/contrib/layers/BUILD index 852d06e1e3..4be55468db 100644 --- a/tensorflow/contrib/layers/BUILD +++ b/tensorflow/contrib/layers/BUILD @@ -390,15 +390,3 @@ py_test( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/layers/kernels/BUILD b/tensorflow/contrib/layers/kernels/BUILD index e407a9ce01..7aae09ff3e 100644 --- a/tensorflow/contrib/layers/kernels/BUILD +++ b/tensorflow/contrib/layers/kernels/BUILD @@ -18,14 +18,3 @@ cc_library( ], alwayslink = 1, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/learn/BUILD b/tensorflow/contrib/learn/BUILD index 9c59150580..924918be4f 100644 --- a/tensorflow/contrib/learn/BUILD +++ b/tensorflow/contrib/learn/BUILD @@ -873,15 +873,3 @@ py_binary( "//tensorflow/python:platform", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/learn/python/learn/datasets/BUILD b/tensorflow/contrib/learn/python/learn/datasets/BUILD index 8bf372841d..2c7215bba3 100644 --- a/tensorflow/contrib/learn/python/learn/datasets/BUILD +++ b/tensorflow/contrib/learn/python/learn/datasets/BUILD @@ -44,18 +44,6 @@ py_binary( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_test( name = "base_test", size = "small", diff --git a/tensorflow/contrib/legacy_seq2seq/BUILD b/tensorflow/contrib/legacy_seq2seq/BUILD index 1fa55132b1..8c2c4fd29c 100644 --- a/tensorflow/contrib/legacy_seq2seq/BUILD +++ b/tensorflow/contrib/legacy_seq2seq/BUILD @@ -60,15 +60,3 @@ cuda_py_tests( ], tags = ["noasan"], # times out b/63678675 ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/libsvm/BUILD b/tensorflow/contrib/libsvm/BUILD index df96402a4f..4dccb9be7c 100644 --- a/tensorflow/contrib/libsvm/BUILD +++ b/tensorflow/contrib/libsvm/BUILD @@ -88,15 +88,3 @@ tf_py_test( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/linalg/BUILD b/tensorflow/contrib/linalg/BUILD index 359255374d..a7812f74d1 100644 --- a/tensorflow/contrib/linalg/BUILD +++ b/tensorflow/contrib/linalg/BUILD @@ -61,15 +61,3 @@ cuda_py_test( shard_count = 4, tags = ["noasan"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/linear_optimizer/BUILD b/tensorflow/contrib/linear_optimizer/BUILD index cea3627ed5..5b89c6cef9 100644 --- a/tensorflow/contrib/linear_optimizer/BUILD +++ b/tensorflow/contrib/linear_optimizer/BUILD @@ -138,14 +138,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/lite/BUILD b/tensorflow/contrib/lite/BUILD index 18efa64507..ac269d540a 100644 --- a/tensorflow/contrib/lite/BUILD +++ b/tensorflow/contrib/lite/BUILD @@ -271,18 +271,3 @@ cc_test( # ], # }), #) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "downloads", - "examples", - "gen", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/examples/label_image/BUILD b/tensorflow/contrib/lite/examples/label_image/BUILD index 959347b549..9322e186a2 100644 --- a/tensorflow/contrib/lite/examples/label_image/BUILD +++ b/tensorflow/contrib/lite/examples/label_image/BUILD @@ -69,15 +69,3 @@ cc_library( # "//testing/base/public:gunit", # ], # ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/java/BUILD b/tensorflow/contrib/lite/java/BUILD index f52d6ba6c5..7f7a2632dd 100644 --- a/tensorflow/contrib/lite/java/BUILD +++ b/tensorflow/contrib/lite/java/BUILD @@ -167,15 +167,3 @@ tflite_jni_binary( "//tensorflow/contrib/lite/java/src/main/native", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/java/demo/app/src/main/BUILD b/tensorflow/contrib/lite/java/demo/app/src/main/BUILD index 5eb749aae6..d6fbef9cc9 100644 --- a/tensorflow/contrib/lite/java/demo/app/src/main/BUILD +++ b/tensorflow/contrib/lite/java/demo/app/src/main/BUILD @@ -27,15 +27,3 @@ android_binary( "@androidsdk//com.android.support:support-v4-25.2.0", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/java/demo/app/src/main/assets/BUILD b/tensorflow/contrib/lite/java/demo/app/src/main/assets/BUILD index dd0cd6c98f..ce68160b68 100644 --- a/tensorflow/contrib/lite/java/demo/app/src/main/assets/BUILD +++ b/tensorflow/contrib/lite/java/demo/app/src/main/assets/BUILD @@ -10,15 +10,3 @@ exports_files( ], ), ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/java/src/main/native/BUILD b/tensorflow/contrib/lite/java/src/main/native/BUILD index 3571182ca9..4399ed2025 100644 --- a/tensorflow/contrib/lite/java/src/main/native/BUILD +++ b/tensorflow/contrib/lite/java/src/main/native/BUILD @@ -95,15 +95,3 @@ exports_files( "version_script.lds", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/java/src/testhelper/java/org/tensorflow/lite/BUILD b/tensorflow/contrib/lite/java/src/testhelper/java/org/tensorflow/lite/BUILD index 2b4f37bc6c..b524246d43 100644 --- a/tensorflow/contrib/lite/java/src/testhelper/java/org/tensorflow/lite/BUILD +++ b/tensorflow/contrib/lite/java/src/testhelper/java/org/tensorflow/lite/BUILD @@ -16,15 +16,3 @@ android_library( "//tensorflow/contrib/lite/java:tensorflowlite_java", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/kernels/BUILD b/tensorflow/contrib/lite/kernels/BUILD index 1450c1e14b..058f995d75 100644 --- a/tensorflow/contrib/lite/kernels/BUILD +++ b/tensorflow/contrib/lite/kernels/BUILD @@ -911,16 +911,4 @@ tf_cc_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tflite_portable_test_suite() diff --git a/tensorflow/contrib/lite/kernels/internal/BUILD b/tensorflow/contrib/lite/kernels/internal/BUILD index aa3957bee1..167c0f1fde 100644 --- a/tensorflow/contrib/lite/kernels/internal/BUILD +++ b/tensorflow/contrib/lite/kernels/internal/BUILD @@ -431,15 +431,3 @@ cc_library( ) exports_files(["optimized/eigen_tensor_reduced_instantiations_oss.h"]) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/models/BUILD b/tensorflow/contrib/lite/models/BUILD index 6a1255b586..efa47b06fa 100644 --- a/tensorflow/contrib/lite/models/BUILD +++ b/tensorflow/contrib/lite/models/BUILD @@ -12,15 +12,3 @@ load("//tensorflow/contrib/lite:build_def.bzl", "tflite_copts") exports_files(glob([ "testdata/*", ])) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/models/smartreply/BUILD b/tensorflow/contrib/lite/models/smartreply/BUILD index 733c3f4c7f..a82d1f2eb6 100644 --- a/tensorflow/contrib/lite/models/smartreply/BUILD +++ b/tensorflow/contrib/lite/models/smartreply/BUILD @@ -86,15 +86,3 @@ cc_test( "@com_google_googletest//:gtest", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/nnapi/BUILD b/tensorflow/contrib/lite/nnapi/BUILD index 402f1e949b..467a2b7a7b 100644 --- a/tensorflow/contrib/lite/nnapi/BUILD +++ b/tensorflow/contrib/lite/nnapi/BUILD @@ -11,15 +11,3 @@ cc_library( ], linkopts = ["-ldl"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/python/BUILD b/tensorflow/contrib/lite/python/BUILD index 411d5c0d27..e70aa51298 100644 --- a/tensorflow/contrib/lite/python/BUILD +++ b/tensorflow/contrib/lite/python/BUILD @@ -118,15 +118,3 @@ py_library( ":convert_saved_model", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/schema/BUILD b/tensorflow/contrib/lite/schema/BUILD index da65ec659c..246ec85fe4 100644 --- a/tensorflow/contrib/lite/schema/BUILD +++ b/tensorflow/contrib/lite/schema/BUILD @@ -70,16 +70,4 @@ cc_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tflite_portable_test_suite() diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index 555ea90034..10e810a6e0 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -373,16 +373,4 @@ tf_cc_test( }), ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tflite_portable_test_suite() diff --git a/tensorflow/contrib/lite/toco/BUILD b/tensorflow/contrib/lite/toco/BUILD index 8ed3e0e14e..bba61627f9 100644 --- a/tensorflow/contrib/lite/toco/BUILD +++ b/tensorflow/contrib/lite/toco/BUILD @@ -420,15 +420,3 @@ tf_cc_test( "@com_google_googletest//:gtest_main", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD index b975cc996b..a2008ddbdb 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD +++ b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD @@ -39,15 +39,3 @@ tf_cc_test( "@com_google_googletest//:gtest_main", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/toco/python/BUILD b/tensorflow/contrib/lite/toco/python/BUILD index 17115047d2..5a40451b3a 100644 --- a/tensorflow/contrib/lite/toco/python/BUILD +++ b/tensorflow/contrib/lite/toco/python/BUILD @@ -63,15 +63,3 @@ tf_py_test( ], tags = ["no_pip"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/toco/tensorflow_graph_matching/BUILD b/tensorflow/contrib/lite/toco/tensorflow_graph_matching/BUILD index 0c1a1141fc..336e94de1e 100644 --- a/tensorflow/contrib/lite/toco/tensorflow_graph_matching/BUILD +++ b/tensorflow/contrib/lite/toco/tensorflow_graph_matching/BUILD @@ -88,15 +88,3 @@ cc_library( "//tensorflow/core:protos_all_cc", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/toco/tflite/BUILD b/tensorflow/contrib/lite/toco/tflite/BUILD index 9d3e1daf12..e0191801a0 100644 --- a/tensorflow/contrib/lite/toco/tflite/BUILD +++ b/tensorflow/contrib/lite/toco/tflite/BUILD @@ -137,15 +137,3 @@ tf_cc_test( "@flatbuffers", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/lite/tools/BUILD b/tensorflow/contrib/lite/tools/BUILD index b5abbc0712..44fde69a1e 100644 --- a/tensorflow/contrib/lite/tools/BUILD +++ b/tensorflow/contrib/lite/tools/BUILD @@ -91,18 +91,6 @@ cc_library( deps = ["//tensorflow/contrib/lite:framework"], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "verifier", srcs = ["verifier.cc"], diff --git a/tensorflow/contrib/lookup/BUILD b/tensorflow/contrib/lookup/BUILD index 8ca03f4193..02b4f80252 100644 --- a/tensorflow/contrib/lookup/BUILD +++ b/tensorflow/contrib/lookup/BUILD @@ -47,15 +47,3 @@ tf_py_test( ], grpc_enabled = True, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/losses/BUILD b/tensorflow/contrib/losses/BUILD index 5694211521..728f75f8ef 100644 --- a/tensorflow/contrib/losses/BUILD +++ b/tensorflow/contrib/losses/BUILD @@ -97,15 +97,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/makefile/BUILD b/tensorflow/contrib/makefile/BUILD index 701eeb44fe..1abb46f4d4 100644 --- a/tensorflow/contrib/makefile/BUILD +++ b/tensorflow/contrib/makefile/BUILD @@ -3,12 +3,3 @@ licenses(["notice"]) # Apache 2.0 package(default_visibility = ["//visibility:private"]) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = ["**/OWNERS"], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/memory_stats/BUILD b/tensorflow/contrib/memory_stats/BUILD index 72424c32e7..63843b993c 100644 --- a/tensorflow/contrib/memory_stats/BUILD +++ b/tensorflow/contrib/memory_stats/BUILD @@ -79,15 +79,3 @@ cuda_py_test( "//tensorflow/python:random_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/meta_graph_transform/BUILD b/tensorflow/contrib/meta_graph_transform/BUILD index 4b5b1c3e15..24400789f8 100644 --- a/tensorflow/contrib/meta_graph_transform/BUILD +++ b/tensorflow/contrib/meta_graph_transform/BUILD @@ -59,15 +59,3 @@ filegroup( "**/*.py", ]), ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/metrics/BUILD b/tensorflow/contrib/metrics/BUILD index e90c525113..5ca42f41c1 100644 --- a/tensorflow/contrib/metrics/BUILD +++ b/tensorflow/contrib/metrics/BUILD @@ -97,14 +97,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/model_pruning/BUILD b/tensorflow/contrib/model_pruning/BUILD index ca3f13479e..f50575b2cf 100644 --- a/tensorflow/contrib/model_pruning/BUILD +++ b/tensorflow/contrib/model_pruning/BUILD @@ -125,15 +125,3 @@ py_library( ":rnn_cells", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/model_pruning/examples/cifar10/BUILD b/tensorflow/contrib/model_pruning/examples/cifar10/BUILD index e7848adcc5..30ea912222 100644 --- a/tensorflow/contrib/model_pruning/examples/cifar10/BUILD +++ b/tensorflow/contrib/model_pruning/examples/cifar10/BUILD @@ -68,15 +68,3 @@ py_binary( "//tensorflow/contrib/model_pruning:pruning", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/mpi_collectives/BUILD b/tensorflow/contrib/mpi_collectives/BUILD index 9f9802b8fe..a7be92a35e 100644 --- a/tensorflow/contrib/mpi_collectives/BUILD +++ b/tensorflow/contrib/mpi_collectives/BUILD @@ -126,15 +126,3 @@ tf_py_test( ], tags = ["manual"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/nccl/BUILD b/tensorflow/contrib/nccl/BUILD index 94d01efee1..6cbfd03881 100644 --- a/tensorflow/contrib/nccl/BUILD +++ b/tensorflow/contrib/nccl/BUILD @@ -141,15 +141,3 @@ cuda_py_test( "notap", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/nearest_neighbor/BUILD b/tensorflow/contrib/nearest_neighbor/BUILD index 9500c18b1d..6fa7624467 100644 --- a/tensorflow/contrib/nearest_neighbor/BUILD +++ b/tensorflow/contrib/nearest_neighbor/BUILD @@ -111,15 +111,3 @@ tf_py_test( "//tensorflow/python:client_testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/nn/BUILD b/tensorflow/contrib/nn/BUILD index 5543eb6c6e..ef7ab22646 100644 --- a/tensorflow/contrib/nn/BUILD +++ b/tensorflow/contrib/nn/BUILD @@ -98,14 +98,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/opt/BUILD b/tensorflow/contrib/opt/BUILD index bacf15bbd6..c57c5e3f29 100644 --- a/tensorflow/contrib/opt/BUILD +++ b/tensorflow/contrib/opt/BUILD @@ -265,14 +265,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/periodic_resample/BUILD b/tensorflow/contrib/periodic_resample/BUILD index bd9078ae76..6ca7fe8b6e 100644 --- a/tensorflow/contrib/periodic_resample/BUILD +++ b/tensorflow/contrib/periodic_resample/BUILD @@ -94,18 +94,6 @@ py_test( # srcs_version = "PY2AND3", # ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "custom_op_sources", srcs = glob( diff --git a/tensorflow/contrib/predictor/BUILD b/tensorflow/contrib/predictor/BUILD index a80f060b91..36e21af618 100644 --- a/tensorflow/contrib/predictor/BUILD +++ b/tensorflow/contrib/predictor/BUILD @@ -8,18 +8,6 @@ exports_files(["LICENSE"]) load("//tensorflow:tensorflow.bzl", "py_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "predictor", srcs = ["__init__.py"], diff --git a/tensorflow/contrib/quantization/BUILD b/tensorflow/contrib/quantization/BUILD index c19a31afb2..2de10e8fae 100644 --- a/tensorflow/contrib/quantization/BUILD +++ b/tensorflow/contrib/quantization/BUILD @@ -49,15 +49,3 @@ filegroup( "**/*.py", ]), ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/quantize/BUILD b/tensorflow/contrib/quantize/BUILD index 0b76296204..b9918fdee1 100644 --- a/tensorflow/contrib/quantize/BUILD +++ b/tensorflow/contrib/quantize/BUILD @@ -246,15 +246,3 @@ py_test( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/receptive_field/BUILD b/tensorflow/contrib/receptive_field/BUILD index e975aeaea7..9325a14745 100644 --- a/tensorflow/contrib/receptive_field/BUILD +++ b/tensorflow/contrib/receptive_field/BUILD @@ -106,15 +106,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/reduce_slice_ops/BUILD b/tensorflow/contrib/reduce_slice_ops/BUILD index b31f4488f5..02b3d66e46 100644 --- a/tensorflow/contrib/reduce_slice_ops/BUILD +++ b/tensorflow/contrib/reduce_slice_ops/BUILD @@ -101,15 +101,3 @@ tf_cc_test( "//tensorflow/core:testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/remote_fused_graph/pylib/BUILD b/tensorflow/contrib/remote_fused_graph/pylib/BUILD index 27f0a7f58f..996b55f9b8 100644 --- a/tensorflow/contrib/remote_fused_graph/pylib/BUILD +++ b/tensorflow/contrib/remote_fused_graph/pylib/BUILD @@ -48,15 +48,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/resampler/BUILD b/tensorflow/contrib/resampler/BUILD index f0ecc8b85a..48345d7030 100644 --- a/tensorflow/contrib/resampler/BUILD +++ b/tensorflow/contrib/resampler/BUILD @@ -85,14 +85,3 @@ cuda_py_test( "//tensorflow/python:array_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/rnn/BUILD b/tensorflow/contrib/rnn/BUILD index 7e5e35d0b5..43c0f75955 100644 --- a/tensorflow/contrib/rnn/BUILD +++ b/tensorflow/contrib/rnn/BUILD @@ -321,19 +321,6 @@ tf_cc_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "tools/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_gen_op_libs( op_lib_names = [ "lstm_ops", diff --git a/tensorflow/contrib/saved_model/BUILD b/tensorflow/contrib/saved_model/BUILD index 245fe07f2b..faad40d335 100644 --- a/tensorflow/contrib/saved_model/BUILD +++ b/tensorflow/contrib/saved_model/BUILD @@ -81,15 +81,3 @@ py_test( "//tensorflow/python/saved_model:utils", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/saved_model/cc/saved_model/BUILD b/tensorflow/contrib/saved_model/cc/saved_model/BUILD index ea4da80ba3..3c616c555b 100644 --- a/tensorflow/contrib/saved_model/cc/saved_model/BUILD +++ b/tensorflow/contrib/saved_model/cc/saved_model/BUILD @@ -49,9 +49,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -filegroup( - name = "all_files", - srcs = glob(["*"]), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/seq2seq/BUILD b/tensorflow/contrib/seq2seq/BUILD index ab80c68b1a..a62069a252 100644 --- a/tensorflow/contrib/seq2seq/BUILD +++ b/tensorflow/contrib/seq2seq/BUILD @@ -211,15 +211,3 @@ cuda_py_test( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/session_bundle/BUILD b/tensorflow/contrib/session_bundle/BUILD index 75a753ed89..31717305e7 100644 --- a/tensorflow/contrib/session_bundle/BUILD +++ b/tensorflow/contrib/session_bundle/BUILD @@ -17,18 +17,6 @@ load( "tf_cc_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), -) - # TODO(b/32673259): add a test to continuously validate these files. filegroup( name = "session_bundle_half_plus_two", diff --git a/tensorflow/contrib/session_bundle/example/BUILD b/tensorflow/contrib/session_bundle/example/BUILD index dbbae01f36..9a56eab431 100644 --- a/tensorflow/contrib/session_bundle/example/BUILD +++ b/tensorflow/contrib/session_bundle/example/BUILD @@ -10,19 +10,6 @@ exports_files(["LICENSE"]) # vardef("PYTHON_BIN_PATH", "/usr/bin/python") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), - visibility = ["//visibility:public"], -) - py_binary( name = "export_half_plus_two", srcs = [ diff --git a/tensorflow/contrib/signal/BUILD b/tensorflow/contrib/signal/BUILD index a83fc20596..fdecceff52 100644 --- a/tensorflow/contrib/signal/BUILD +++ b/tensorflow/contrib/signal/BUILD @@ -130,15 +130,3 @@ cuda_py_tests( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/slim/BUILD b/tensorflow/contrib/slim/BUILD index c2f106c2b2..516e3ea073 100644 --- a/tensorflow/contrib/slim/BUILD +++ b/tensorflow/contrib/slim/BUILD @@ -178,15 +178,3 @@ py_test( "//tensorflow/python:summary", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/slim/python/slim/data/BUILD b/tensorflow/contrib/slim/python/slim/data/BUILD index 5daabbd62e..dc12e67fc6 100644 --- a/tensorflow/contrib/slim/python/slim/data/BUILD +++ b/tensorflow/contrib/slim/python/slim/data/BUILD @@ -193,15 +193,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/slim/python/slim/nets/BUILD b/tensorflow/contrib/slim/python/slim/nets/BUILD index 7f03aaf085..8bbdf96384 100644 --- a/tensorflow/contrib/slim/python/slim/nets/BUILD +++ b/tensorflow/contrib/slim/python/slim/nets/BUILD @@ -317,15 +317,3 @@ py_test( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/solvers/BUILD b/tensorflow/contrib/solvers/BUILD index 87b67486ad..5247288d54 100644 --- a/tensorflow/contrib/solvers/BUILD +++ b/tensorflow/contrib/solvers/BUILD @@ -93,16 +93,3 @@ cuda_py_test( "//tensorflow/python:platform_test", ], ) - -# All files -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/sparsemax/BUILD b/tensorflow/contrib/sparsemax/BUILD index fcfaa2aba4..b729fff261 100644 --- a/tensorflow/contrib/sparsemax/BUILD +++ b/tensorflow/contrib/sparsemax/BUILD @@ -65,15 +65,3 @@ cuda_py_tests( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/specs/BUILD b/tensorflow/contrib/specs/BUILD index 084953a0a2..055b04db8a 100644 --- a/tensorflow/contrib/specs/BUILD +++ b/tensorflow/contrib/specs/BUILD @@ -60,15 +60,3 @@ tf_py_test( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/staging/BUILD b/tensorflow/contrib/staging/BUILD index bc4a289468..0c86f3db1d 100644 --- a/tensorflow/contrib/staging/BUILD +++ b/tensorflow/contrib/staging/BUILD @@ -6,18 +6,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "staging", srcs = ["__init__.py"], diff --git a/tensorflow/contrib/stat_summarizer/BUILD b/tensorflow/contrib/stat_summarizer/BUILD index 5fd02efbf6..d4096751c4 100644 --- a/tensorflow/contrib/stat_summarizer/BUILD +++ b/tensorflow/contrib/stat_summarizer/BUILD @@ -32,15 +32,3 @@ tf_py_test( "//tensorflow/python:variables", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/stateless/BUILD b/tensorflow/contrib/stateless/BUILD index 6e259e1d32..dcbef2881d 100644 --- a/tensorflow/contrib/stateless/BUILD +++ b/tensorflow/contrib/stateless/BUILD @@ -38,15 +38,3 @@ cuda_py_test( "//tensorflow/python:random_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/summary/BUILD b/tensorflow/contrib/summary/BUILD index 80563c5e15..fda1367b15 100644 --- a/tensorflow/contrib/summary/BUILD +++ b/tensorflow/contrib/summary/BUILD @@ -83,18 +83,6 @@ py_library( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # NOTE: target cannot be testonly because it needs to be in the pip # package. Sigh. py_library( diff --git a/tensorflow/contrib/tensor_forest/BUILD b/tensorflow/contrib/tensor_forest/BUILD index 1e4cc3f095..11a59ec22b 100644 --- a/tensorflow/contrib/tensor_forest/BUILD +++ b/tensorflow/contrib/tensor_forest/BUILD @@ -16,20 +16,6 @@ package(default_visibility = ["//visibility:public"]) exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "kernels/v4/*", - "proto/*", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # ---------------------------------- V2 ops ------------------------------------------# filegroup( name = "v2_op_sources", diff --git a/tensorflow/contrib/tensor_forest/hybrid/BUILD b/tensorflow/contrib/tensor_forest/hybrid/BUILD index a2a3b485f6..b7185e09c7 100644 --- a/tensorflow/contrib/tensor_forest/hybrid/BUILD +++ b/tensorflow/contrib/tensor_forest/hybrid/BUILD @@ -11,18 +11,6 @@ package(default_visibility = ["//visibility:public"]) exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "custom_op_sources", srcs = glob( diff --git a/tensorflow/contrib/tensor_forest/kernels/v4/BUILD b/tensorflow/contrib/tensor_forest/kernels/v4/BUILD index 794b76d858..b1b1559383 100644 --- a/tensorflow/contrib/tensor_forest/kernels/v4/BUILD +++ b/tensorflow/contrib/tensor_forest/kernels/v4/BUILD @@ -11,11 +11,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob(["**/*"]), -) - DECISION_TREE_RESOURCE_DEPS = [ ":decision_node_evaluator", ":input_data", diff --git a/tensorflow/contrib/tensor_forest/proto/BUILD b/tensorflow/contrib/tensor_forest/proto/BUILD index 1cfef44af1..04fd6a9839 100644 --- a/tensorflow/contrib/tensor_forest/proto/BUILD +++ b/tensorflow/contrib/tensor_forest/proto/BUILD @@ -6,14 +6,6 @@ load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") package(default_visibility = ["//visibility:public"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "fertile_stats_proto", srcs = ["fertile_stats.proto"], diff --git a/tensorflow/contrib/tensorboard/BUILD b/tensorflow/contrib/tensorboard/BUILD index d833744d0c..f4efd9717d 100644 --- a/tensorflow/contrib/tensorboard/BUILD +++ b/tensorflow/contrib/tensorboard/BUILD @@ -88,15 +88,3 @@ py_test( "//tensorflow/python:platform", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/tensorboard/db/BUILD b/tensorflow/contrib/tensorboard/db/BUILD index 4175d8adb5..3f6b4cdc9a 100644 --- a/tensorflow/contrib/tensorboard/db/BUILD +++ b/tensorflow/contrib/tensorboard/db/BUILD @@ -135,9 +135,3 @@ tf_cc_binary( "//tensorflow/core/lib/db:sqlite", ], ) - -filegroup( - name = "all_files", - srcs = glob(["*"]), - visibility = ["//tensorflow:__pkg__"], -) diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 906cc3f034..2f316767b3 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -272,15 +272,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/testing/BUILD b/tensorflow/contrib/testing/BUILD index 0be6aa755b..8a40e111d7 100644 --- a/tensorflow/contrib/testing/BUILD +++ b/tensorflow/contrib/testing/BUILD @@ -22,15 +22,3 @@ py_library( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/text/BUILD b/tensorflow/contrib/text/BUILD index 698fdd830f..38d91f7e49 100644 --- a/tensorflow/contrib/text/BUILD +++ b/tensorflow/contrib/text/BUILD @@ -111,14 +111,3 @@ py_test( "//tensorflow/python:training", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/tfprof/BUILD b/tensorflow/contrib/tfprof/BUILD index 28adce71d4..e7f4ebdd36 100644 --- a/tensorflow/contrib/tfprof/BUILD +++ b/tensorflow/contrib/tfprof/BUILD @@ -20,15 +20,3 @@ py_library( "//tensorflow/python/profiler:tfprof_logger", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/timeseries/BUILD b/tensorflow/contrib/timeseries/BUILD index 6ba069778c..f2b8786a52 100644 --- a/tensorflow/contrib/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/BUILD @@ -31,15 +31,3 @@ py_library( "//tensorflow/contrib/timeseries/python/timeseries/state_space_models:test_utils", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/timeseries/examples/BUILD b/tensorflow/contrib/timeseries/examples/BUILD index bb86ecb220..40cf9147b3 100644 --- a/tensorflow/contrib/timeseries/examples/BUILD +++ b/tensorflow/contrib/timeseries/examples/BUILD @@ -106,15 +106,3 @@ py_test( "//tensorflow/python/estimator:estimator_py", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/timeseries/python/timeseries/BUILD b/tensorflow/contrib/timeseries/python/timeseries/BUILD index ed3ed4c0e1..55a25e39fe 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/BUILD @@ -442,15 +442,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD index c86d06e923..ca25ccd2b8 100644 --- a/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD +++ b/tensorflow/contrib/timeseries/python/timeseries/state_space_models/BUILD @@ -268,15 +268,3 @@ py_library( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD index 95dc6f5ced..3e32a7a85c 100644 --- a/tensorflow/contrib/tpu/BUILD +++ b/tensorflow/contrib/tpu/BUILD @@ -283,16 +283,3 @@ tf_py_test( "//tensorflow/python:framework_test_lib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/contrib/tpu/profiler/BUILD b/tensorflow/contrib/tpu/profiler/BUILD index 56ddd7eff1..1c32993e8e 100644 --- a/tensorflow/contrib/tpu/profiler/BUILD +++ b/tensorflow/contrib/tpu/profiler/BUILD @@ -6,18 +6,6 @@ load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library_cc") load("//tensorflow/core:platform/default/build_config.bzl", "tf_additional_all_protos") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "tpu_profiler_proto", srcs = ["tpu_profiler.proto"], diff --git a/tensorflow/contrib/tpu/proto/BUILD b/tensorflow/contrib/tpu/proto/BUILD index e166098567..fcfbbe1a21 100644 --- a/tensorflow/contrib/tpu/proto/BUILD +++ b/tensorflow/contrib/tpu/proto/BUILD @@ -4,17 +4,6 @@ exports_files(["LICENSE"]) load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "tpu_embedding_config_proto", srcs = [ diff --git a/tensorflow/contrib/training/BUILD b/tensorflow/contrib/training/BUILD index 6ae2f38252..4d2bfd3e43 100644 --- a/tensorflow/contrib/training/BUILD +++ b/tensorflow/contrib/training/BUILD @@ -308,18 +308,6 @@ py_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_proto_library( name = "protos_all", srcs = glob(["**/*.proto"]), diff --git a/tensorflow/contrib/util/BUILD b/tensorflow/contrib/util/BUILD index 6c766e4f1c..d9ccda8e89 100644 --- a/tensorflow/contrib/util/BUILD +++ b/tensorflow/contrib/util/BUILD @@ -75,15 +75,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/contrib/verbs/BUILD b/tensorflow/contrib/verbs/BUILD index 80a5d07ea4..9720fd6e86 100644 --- a/tensorflow/contrib/verbs/BUILD +++ b/tensorflow/contrib/verbs/BUILD @@ -11,18 +11,6 @@ load("//tensorflow:tensorflow.bzl", "tf_cuda_library") exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "c_srcs", data = glob([ diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 712106492b..d46241450c 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -149,6 +149,8 @@ load( "if_mkl", ) +exports_files(["ops/ops.pbtxt"]) + # ----------------------------------------------------------------------------- # Public targets @@ -3851,18 +3853,6 @@ cc_library( # ----------------------------------------------------------------------------- # Google-internal targets go here (must be at the end). -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - alias( name = "android_srcs_no_runtime", actual = ":mobile_srcs_no_runtime", diff --git a/tensorflow/core/api_def/BUILD b/tensorflow/core/api_def/BUILD index 58dbac4e8e..19d6438809 100644 --- a/tensorflow/core/api_def/BUILD +++ b/tensorflow/core/api_def/BUILD @@ -17,18 +17,6 @@ load( "tf_cc_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "base_api_def", srcs = glob(["base_api/*"]), diff --git a/tensorflow/core/common_runtime/eager/BUILD b/tensorflow/core/common_runtime/eager/BUILD index 9e8baab618..941a0e61c7 100644 --- a/tensorflow/core/common_runtime/eager/BUILD +++ b/tensorflow/core/common_runtime/eager/BUILD @@ -135,21 +135,6 @@ tf_cc_test( ], ) -# ----------------------------------------------------------------------------- -# Google-internal targets. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "execute", srcs = ["execute.cc"], diff --git a/tensorflow/core/debug/BUILD b/tensorflow/core/debug/BUILD index f6fe9edb02..5fab740e92 100644 --- a/tensorflow/core/debug/BUILD +++ b/tensorflow/core/debug/BUILD @@ -339,18 +339,3 @@ cc_library( # ], # visibility = ["//visibility:public"], # ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/core/distributed_runtime/BUILD b/tensorflow/core/distributed_runtime/BUILD index 434626bd2d..b07cb8cdcb 100644 --- a/tensorflow/core/distributed_runtime/BUILD +++ b/tensorflow/core/distributed_runtime/BUILD @@ -7,18 +7,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "c_srcs", data = glob([ diff --git a/tensorflow/core/distributed_runtime/rpc/BUILD b/tensorflow/core/distributed_runtime/rpc/BUILD index 9dae1b9859..9c655bfa31 100644 --- a/tensorflow/core/distributed_runtime/rpc/BUILD +++ b/tensorflow/core/distributed_runtime/rpc/BUILD @@ -5,18 +5,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "c_srcs", data = glob([ diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD index 2ca9b720ee..9dcc6765f5 100644 --- a/tensorflow/core/grappler/BUILD +++ b/tensorflow/core/grappler/BUILD @@ -3,18 +3,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") load("//tensorflow:tensorflow.bzl", "tf_cuda_library") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "op_types", srcs = ["op_types.cc"], diff --git a/tensorflow/core/grappler/clusters/BUILD b/tensorflow/core/grappler/clusters/BUILD index b653f902e8..9ecf5a6cf7 100644 --- a/tensorflow/core/grappler/clusters/BUILD +++ b/tensorflow/core/grappler/clusters/BUILD @@ -8,18 +8,6 @@ load( "tf_cuda_tests_tags", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - config_setting( name = "xsmm", licenses = ["notice"], diff --git a/tensorflow/core/grappler/costs/BUILD b/tensorflow/core/grappler/costs/BUILD index df5a26f475..33949319d5 100644 --- a/tensorflow/core/grappler/costs/BUILD +++ b/tensorflow/core/grappler/costs/BUILD @@ -6,18 +6,6 @@ load( "tf_protos_grappler", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "graph_properties_testdata", srcs = glob([ diff --git a/tensorflow/core/grappler/inputs/BUILD b/tensorflow/core/grappler/inputs/BUILD index b683216590..ffa204028c 100644 --- a/tensorflow/core/grappler/inputs/BUILD +++ b/tensorflow/core/grappler/inputs/BUILD @@ -2,18 +2,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "utils", srcs = [ diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 19ff788aba..0d3a488f85 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -12,18 +12,6 @@ load( "tf_protos_grappler", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "static_schedule", srcs = ["static_schedule.cc"], diff --git a/tensorflow/core/grappler/utils/BUILD b/tensorflow/core/grappler/utils/BUILD index 939031c44b..baf24c2505 100644 --- a/tensorflow/core/grappler/utils/BUILD +++ b/tensorflow/core/grappler/utils/BUILD @@ -2,18 +2,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "scc", srcs = ["scc.cc"], diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index b469c01881..ca54978421 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -6147,18 +6147,6 @@ tf_kernel_library( # ----------------------------------------------------------------------------- # Google-internal targets. These must be at the end for syncrepo. -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - # Library to link with when compiling the cwise_op kernels directly, # e.g. for selective registration. # should not be linked by projects that also link the cwise_op library. diff --git a/tensorflow/core/kernels/batching_util/BUILD b/tensorflow/core/kernels/batching_util/BUILD index 4397410a5c..de05c647d6 100644 --- a/tensorflow/core/kernels/batching_util/BUILD +++ b/tensorflow/core/kernels/batching_util/BUILD @@ -8,18 +8,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "tf_cc_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "**/google_*", - ], - ), -) - cc_library( name = "periodic_function_dynamic", srcs = ["periodic_function.cc"], diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index a8784e3656..8c4f0218ee 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -13,18 +13,6 @@ load( "tf_cc_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "stats_aggregator", hdrs = ["stats_aggregator.h"], diff --git a/tensorflow/core/kernels/data/sql/BUILD b/tensorflow/core/kernels/data/sql/BUILD index f4698bdaf7..dc59120875 100644 --- a/tensorflow/core/kernels/data/sql/BUILD +++ b/tensorflow/core/kernels/data/sql/BUILD @@ -7,18 +7,6 @@ package( licenses(["notice"]) # Apache 2.0 -filegroup( - name = "all_files", - srcs = glob( - include = ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "sql", srcs = [ diff --git a/tensorflow/core/kernels/fuzzing/BUILD b/tensorflow/core/kernels/fuzzing/BUILD index 9a7eca03ce..aab4b009b5 100644 --- a/tensorflow/core/kernels/fuzzing/BUILD +++ b/tensorflow/core/kernels/fuzzing/BUILD @@ -17,18 +17,6 @@ cc_library( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - load("//tensorflow/core/kernels/fuzzing:tf_ops_fuzz_target_lib.bzl", "tf_ops_fuzz_target_lib") tf_ops_fuzz_target_lib("identity") diff --git a/tensorflow/core/kernels/hexagon/BUILD b/tensorflow/core/kernels/hexagon/BUILD index 7688305019..4870d9ae20 100644 --- a/tensorflow/core/kernels/hexagon/BUILD +++ b/tensorflow/core/kernels/hexagon/BUILD @@ -13,18 +13,6 @@ load( "tf_kernel_library", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_cc_test( name = "graph_transferer_test", size = "small", diff --git a/tensorflow/core/kernels/neon/BUILD b/tensorflow/core/kernels/neon/BUILD index c3d24e50ef..313d40c082 100644 --- a/tensorflow/core/kernels/neon/BUILD +++ b/tensorflow/core/kernels/neon/BUILD @@ -12,18 +12,6 @@ load( "tf_kernel_library", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_kernel_library( name = "neon_depthwise_conv_op", hdrs = [ diff --git a/tensorflow/core/lib/db/BUILD b/tensorflow/core/lib/db/BUILD index 9ff87e8d66..ce09c2009a 100644 --- a/tensorflow/core/lib/db/BUILD +++ b/tensorflow/core/lib/db/BUILD @@ -42,9 +42,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -filegroup( - name = "all_files", - srcs = glob(["*"]), - visibility = ["//tensorflow:__pkg__"], -) diff --git a/tensorflow/core/ops/compat/BUILD b/tensorflow/core/ops/compat/BUILD index 6cdb1586bc..c613ab144f 100644 --- a/tensorflow/core/ops/compat/BUILD +++ b/tensorflow/core/ops/compat/BUILD @@ -57,18 +57,3 @@ tf_cc_binary( "//tensorflow/core:lib", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/core/platform/cloud/BUILD b/tensorflow/core/platform/cloud/BUILD index 21636641e7..3ee7be3c4e 100644 --- a/tensorflow/core/platform/cloud/BUILD +++ b/tensorflow/core/platform/cloud/BUILD @@ -14,20 +14,6 @@ load( "if_windows", ) -filegroup( - name = "all_files", - srcs = glob( - include = [ - "**/*", - ], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "expiring_lru_cache", hdrs = ["expiring_lru_cache.h"], diff --git a/tensorflow/core/platform/default/build_config/BUILD b/tensorflow/core/platform/default/build_config/BUILD index afb1d84d14..447056eb4b 100644 --- a/tensorflow/core/platform/default/build_config/BUILD +++ b/tensorflow/core/platform/default/build_config/BUILD @@ -223,15 +223,3 @@ alias( actual = ":mobile_srcs", visibility = ["//visibility:public"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/core/platform/hadoop/BUILD b/tensorflow/core/platform/hadoop/BUILD index 774a439855..7c38c399bd 100644 --- a/tensorflow/core/platform/hadoop/BUILD +++ b/tensorflow/core/platform/hadoop/BUILD @@ -12,18 +12,6 @@ load( "tf_cc_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "hadoop_file_system", srcs = ["hadoop_file_system.cc"], diff --git a/tensorflow/core/platform/s3/BUILD b/tensorflow/core/platform/s3/BUILD index 3a0ad2e9bd..21038cfeb1 100644 --- a/tensorflow/core/platform/s3/BUILD +++ b/tensorflow/core/platform/s3/BUILD @@ -13,18 +13,6 @@ load( "tf_cc_test", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - tf_cc_binary( name = "s3_file_system.so", srcs = [ diff --git a/tensorflow/core/profiler/BUILD b/tensorflow/core/profiler/BUILD index 5ce6f1046d..3d3203cdaa 100644 --- a/tensorflow/core/profiler/BUILD +++ b/tensorflow/core/profiler/BUILD @@ -4,21 +4,6 @@ package( licenses(["notice"]) # Apache 2.0 -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - load("//tensorflow:tensorflow.bzl", "tf_cc_binary") load("//tensorflow/core:platform/default/build_config.bzl", "tf_proto_library") load("//tensorflow/core:platform/default/build_config.bzl", "tf_additional_all_protos") diff --git a/tensorflow/core/profiler/internal/BUILD b/tensorflow/core/profiler/internal/BUILD index 05a798bff8..8dcfde9a2a 100644 --- a/tensorflow/core/profiler/internal/BUILD +++ b/tensorflow/core/profiler/internal/BUILD @@ -365,17 +365,3 @@ cc_library( "//tensorflow/core:regexp_internal", ], ) -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/core/profiler/internal/advisor/BUILD b/tensorflow/core/profiler/internal/advisor/BUILD index 40cfd1e12e..1fedb05ae3 100644 --- a/tensorflow/core/profiler/internal/advisor/BUILD +++ b/tensorflow/core/profiler/internal/advisor/BUILD @@ -73,18 +73,3 @@ tf_cc_test( "//tensorflow/core/profiler/internal:tfprof_tf_testlib", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/core/util/ctc/BUILD b/tensorflow/core/util/ctc/BUILD index 1521349e4d..317420204e 100644 --- a/tensorflow/core/util/ctc/BUILD +++ b/tensorflow/core/util/ctc/BUILD @@ -26,18 +26,6 @@ alias( actual = ":mobile_srcs", ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "ctc", deps = [ diff --git a/tensorflow/core/util/tensor_bundle/BUILD b/tensorflow/core/util/tensor_bundle/BUILD index 166bd0f659..648358606c 100644 --- a/tensorflow/core/util/tensor_bundle/BUILD +++ b/tensorflow/core/util/tensor_bundle/BUILD @@ -75,18 +75,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/adding_an_op/BUILD b/tensorflow/examples/adding_an_op/BUILD index b3ed6589ed..cf8054be6a 100644 --- a/tensorflow/examples/adding_an_op/BUILD +++ b/tensorflow/examples/adding_an_op/BUILD @@ -139,15 +139,3 @@ tf_cc_binary( "//tensorflow/core:framework", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/android/BUILD b/tensorflow/examples/android/BUILD index 1214647797..a088d7cf2f 100644 --- a/tensorflow/examples/android/BUILD +++ b/tensorflow/examples/android/BUILD @@ -100,22 +100,6 @@ filegroup( ) # LINT.ThenChange(//tensorflow/examples/android/download-models.gradle) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - "gradleBuild/**", - "libs/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - filegroup( name = "java_files", srcs = glob(["src/**/*.java"]), diff --git a/tensorflow/examples/benchmark/BUILD b/tensorflow/examples/benchmark/BUILD index c4bb0a5bd9..98611a9aad 100644 --- a/tensorflow/examples/benchmark/BUILD +++ b/tensorflow/examples/benchmark/BUILD @@ -23,9 +23,3 @@ tf_py_logged_benchmark( name = "sample_logged_benchmark", target = "//tensorflow/examples/benchmark:sample_benchmark", ) - -filegroup( - name = "all_files", - srcs = glob(["**/*"]), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/get_started/regression/BUILD b/tensorflow/examples/get_started/regression/BUILD index 577b970c90..bee94d7d90 100644 --- a/tensorflow/examples/get_started/regression/BUILD +++ b/tensorflow/examples/get_started/regression/BUILD @@ -2,18 +2,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_test( name = "test", size = "medium", diff --git a/tensorflow/examples/how_tos/reading_data/BUILD b/tensorflow/examples/how_tos/reading_data/BUILD index 4a43585d53..64a054d371 100644 --- a/tensorflow/examples/how_tos/reading_data/BUILD +++ b/tensorflow/examples/how_tos/reading_data/BUILD @@ -54,15 +54,3 @@ py_binary( "//tensorflow/examples/tutorials/mnist:input_data", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/image_retraining/BUILD b/tensorflow/examples/image_retraining/BUILD index 9f9244a74c..ecd79a3b00 100644 --- a/tensorflow/examples/image_retraining/BUILD +++ b/tensorflow/examples/image_retraining/BUILD @@ -49,15 +49,3 @@ py_test( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/label_image/BUILD b/tensorflow/examples/label_image/BUILD index 2abbe9dacc..c50fd93d03 100644 --- a/tensorflow/examples/label_image/BUILD +++ b/tensorflow/examples/label_image/BUILD @@ -9,6 +9,8 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) +exports_files(["data/grace_hopper.jpg"]) + load("//tensorflow:tensorflow.bzl", "tf_cc_binary") tf_cc_binary( @@ -60,17 +62,3 @@ py_binary( "//tensorflow:tensorflow_py", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/learn/BUILD b/tensorflow/examples/learn/BUILD index aba7f600b5..bdbcb0b163 100644 --- a/tensorflow/examples/learn/BUILD +++ b/tensorflow/examples/learn/BUILD @@ -152,15 +152,3 @@ sh_test( "notap", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/multibox_detector/BUILD b/tensorflow/examples/multibox_detector/BUILD index 91a5bfa51c..4f9908cd52 100644 --- a/tensorflow/examples/multibox_detector/BUILD +++ b/tensorflow/examples/multibox_detector/BUILD @@ -27,17 +27,3 @@ tf_cc_binary( "//tensorflow/core:tensorflow", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/saved_model/BUILD b/tensorflow/examples/saved_model/BUILD index 1cdf5ec6e1..ebefc6576d 100644 --- a/tensorflow/examples/saved_model/BUILD +++ b/tensorflow/examples/saved_model/BUILD @@ -8,19 +8,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "g3doc/sitemap.md", - ], - ), - visibility = ["//visibility:public"], -) - py_binary( name = "saved_model_half_plus_two", srcs = [ diff --git a/tensorflow/examples/speech_commands/BUILD b/tensorflow/examples/speech_commands/BUILD index 12479211c3..13bca34a86 100644 --- a/tensorflow/examples/speech_commands/BUILD +++ b/tensorflow/examples/speech_commands/BUILD @@ -245,15 +245,3 @@ tf_cc_binary( "//tensorflow/core:protos_all_cc", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/tutorials/estimators/BUILD b/tensorflow/examples/tutorials/estimators/BUILD index ecbc1a431d..bab609f208 100644 --- a/tensorflow/examples/tutorials/estimators/BUILD +++ b/tensorflow/examples/tutorials/estimators/BUILD @@ -20,15 +20,3 @@ py_binary( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/tutorials/layers/BUILD b/tensorflow/examples/tutorials/layers/BUILD index f8a29c79c6..aad78b1840 100644 --- a/tensorflow/examples/tutorials/layers/BUILD +++ b/tensorflow/examples/tutorials/layers/BUILD @@ -19,15 +19,3 @@ py_binary( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/tutorials/mnist/BUILD b/tensorflow/examples/tutorials/mnist/BUILD index 6d4e67063d..aa1b2ec2db 100644 --- a/tensorflow/examples/tutorials/mnist/BUILD +++ b/tensorflow/examples/tutorials/mnist/BUILD @@ -132,15 +132,3 @@ py_test( "//tensorflow:tensorflow_py", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/tutorials/monitors/BUILD b/tensorflow/examples/tutorials/monitors/BUILD index 4220e8144d..1c49e3fe53 100644 --- a/tensorflow/examples/tutorials/monitors/BUILD +++ b/tensorflow/examples/tutorials/monitors/BUILD @@ -23,15 +23,3 @@ py_binary( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/examples/tutorials/word2vec/BUILD b/tensorflow/examples/tutorials/word2vec/BUILD index bfcf459269..2e19c038bd 100644 --- a/tensorflow/examples/tutorials/word2vec/BUILD +++ b/tensorflow/examples/tutorials/word2vec/BUILD @@ -21,14 +21,3 @@ py_binary( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/examples/wav_to_spectrogram/BUILD b/tensorflow/examples/wav_to_spectrogram/BUILD index c99870c686..cc8835728d 100644 --- a/tensorflow/examples/wav_to_spectrogram/BUILD +++ b/tensorflow/examples/wav_to_spectrogram/BUILD @@ -49,17 +49,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/java/BUILD b/tensorflow/java/BUILD index 5a533e3b60..acaf1a44eb 100644 --- a/tensorflow/java/BUILD +++ b/tensorflow/java/BUILD @@ -388,15 +388,3 @@ genrule( cmd = "cp $< $@", output_to_bindir = 1, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 09c1965d7e..0c3c3c4e06 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -4389,18 +4389,6 @@ py_test( ], ) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cuda_py_test( name = "accumulate_n_benchmark", size = "large", diff --git a/tensorflow/python/data/BUILD b/tensorflow/python/data/BUILD index b5bee36dcd..3e08c1587e 100644 --- a/tensorflow/python/data/BUILD +++ b/tensorflow/python/data/BUILD @@ -15,15 +15,3 @@ py_library( "//tensorflow/python/data/ops:readers", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index 8b8adefa65..ed0c11e6c1 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -367,15 +367,3 @@ tf_py_test( "no_windows", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/data/ops/BUILD b/tensorflow/python/data/ops/BUILD index 3119ab0037..fa2e86eab1 100644 --- a/tensorflow/python/data/ops/BUILD +++ b/tensorflow/python/data/ops/BUILD @@ -59,15 +59,3 @@ py_library( "//tensorflow/python/eager:context", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/data/util/BUILD b/tensorflow/python/data/util/BUILD index b1bdbdab37..0fc32d51b9 100644 --- a/tensorflow/python/data/util/BUILD +++ b/tensorflow/python/data/util/BUILD @@ -109,15 +109,3 @@ py_test( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/debug/BUILD b/tensorflow/python/debug/BUILD index 512d292ee2..4195586313 100644 --- a/tensorflow/python/debug/BUILD +++ b/tensorflow/python/debug/BUILD @@ -1095,15 +1095,3 @@ sh_test( ":offline_analyzer", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 0e089a26eb..8c0d3feece 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -398,21 +398,6 @@ py_test( ], ) -# ----------------------------------------------------------------------------- -# Google-internal targets. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "imperative_grad", srcs = ["imperative_grad.py"], diff --git a/tensorflow/python/estimator/BUILD b/tensorflow/python/estimator/BUILD index 5afb5a7dd5..1fcff18a3a 100644 --- a/tensorflow/python/estimator/BUILD +++ b/tensorflow/python/estimator/BUILD @@ -9,18 +9,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "estimator_py", srcs = ["estimator_lib.py"], diff --git a/tensorflow/python/feature_column/BUILD b/tensorflow/python/feature_column/BUILD index 238a90b67d..0ae9900a1d 100644 --- a/tensorflow/python/feature_column/BUILD +++ b/tensorflow/python/feature_column/BUILD @@ -6,18 +6,6 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "py_test") -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_library( name = "feature_column_py", srcs = ["feature_column_lib.py"], diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index 16033e9b8f..2a06907f49 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -868,15 +868,3 @@ py_library( "//third_party/py/numpy", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 05f34db14b..ea210346c1 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -2945,15 +2945,3 @@ tf_py_test( "//tensorflow/python/eager:tape", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/kernel_tests/distributions/BUILD b/tensorflow/python/kernel_tests/distributions/BUILD index e220d05692..f3cc9636f9 100644 --- a/tensorflow/python/kernel_tests/distributions/BUILD +++ b/tensorflow/python/kernel_tests/distributions/BUILD @@ -280,15 +280,3 @@ cuda_py_test( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/kernel_tests/linalg/BUILD b/tensorflow/python/kernel_tests/linalg/BUILD index fd1b5bab6f..9555e51099 100644 --- a/tensorflow/python/kernel_tests/linalg/BUILD +++ b/tensorflow/python/kernel_tests/linalg/BUILD @@ -140,15 +140,3 @@ cuda_py_test( ], shard_count = 5, ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/kernel_tests/random/BUILD b/tensorflow/python/kernel_tests/random/BUILD index 88a4ddf7f2..acd7566eec 100644 --- a/tensorflow/python/kernel_tests/random/BUILD +++ b/tensorflow/python/kernel_tests/random/BUILD @@ -121,15 +121,3 @@ cuda_py_test( "//tensorflow/python:random_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/ops/distributions/BUILD b/tensorflow/python/ops/distributions/BUILD index 50b956a267..9d9ede7ad7 100644 --- a/tensorflow/python/ops/distributions/BUILD +++ b/tensorflow/python/ops/distributions/BUILD @@ -26,15 +26,3 @@ py_library( "@six_archive//:six", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/ops/linalg/BUILD b/tensorflow/python/ops/linalg/BUILD index ce8c1580fe..07659ef44c 100644 --- a/tensorflow/python/ops/linalg/BUILD +++ b/tensorflow/python/ops/linalg/BUILD @@ -34,15 +34,3 @@ py_library( "//tensorflow/python:special_math_ops", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/ops/losses/BUILD b/tensorflow/python/ops/losses/BUILD index 07741e0c3c..4aea0265a7 100644 --- a/tensorflow/python/ops/losses/BUILD +++ b/tensorflow/python/ops/losses/BUILD @@ -43,15 +43,3 @@ py_test( "//tensorflow/python:framework_for_generated_wrappers", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/profiler/BUILD b/tensorflow/python/profiler/BUILD index c815aad0a0..0654104a34 100644 --- a/tensorflow/python/profiler/BUILD +++ b/tensorflow/python/profiler/BUILD @@ -156,18 +156,3 @@ py_test( "@com_google_pprof//:pprof_proto_py", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/profiler/internal/BUILD b/tensorflow/python/profiler/internal/BUILD index 362a1c49e6..994206cd63 100644 --- a/tensorflow/python/profiler/internal/BUILD +++ b/tensorflow/python/profiler/internal/BUILD @@ -70,18 +70,3 @@ cuda_py_test( "no_pip", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/saved_model/BUILD b/tensorflow/python/saved_model/BUILD index 30e0a099d8..2609a5d222 100644 --- a/tensorflow/python/saved_model/BUILD +++ b/tensorflow/python/saved_model/BUILD @@ -235,15 +235,3 @@ py_test( # ----------------------------------------------------------------------------- # Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/python/tools/BUILD b/tensorflow/python/tools/BUILD index 1de1adcfbc..6e39ce8c80 100644 --- a/tensorflow/python/tools/BUILD +++ b/tensorflow/python/tools/BUILD @@ -258,17 +258,3 @@ py_test( "//tensorflow/core:protos_all_py", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - "bin/**", - "gen/**", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/api/generator/BUILD b/tensorflow/tools/api/generator/BUILD index d9b0260c9f..6722536358 100644 --- a/tensorflow/tools/api/generator/BUILD +++ b/tensorflow/tools/api/generator/BUILD @@ -5,18 +5,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - py_binary( name = "create_python_api", srcs = ["create_python_api.py"], diff --git a/tensorflow/tools/api/golden/BUILD b/tensorflow/tools/api/golden/BUILD index 08436396a6..ebdf42df2c 100644 --- a/tensorflow/tools/api/golden/BUILD +++ b/tensorflow/tools/api/golden/BUILD @@ -10,15 +10,3 @@ filegroup( name = "api_golden", srcs = glob(["*.pbtxt"]), ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/api/lib/BUILD b/tensorflow/tools/api/lib/BUILD index 2d3b838957..3f4fb91042 100644 --- a/tensorflow/tools/api/lib/BUILD +++ b/tensorflow/tools/api/lib/BUILD @@ -26,15 +26,3 @@ py_library( "//tensorflow/python:util", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/api/tests/BUILD b/tensorflow/tools/api/tests/BUILD index 15bf1abb5f..0dc154b6d2 100644 --- a/tensorflow/tools/api/tests/BUILD +++ b/tensorflow/tools/api/tests/BUILD @@ -42,15 +42,3 @@ tf_cc_binary( "//tensorflow/core:op_gen_lib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/benchmark/BUILD b/tensorflow/tools/benchmark/BUILD index 6ed2594e6a..566a172ea7 100644 --- a/tensorflow/tools/benchmark/BUILD +++ b/tensorflow/tools/benchmark/BUILD @@ -90,12 +90,3 @@ tf_cc_binary( visibility = ["//visibility:public"], deps = [":benchmark_model_lib"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = ["**/OWNERS"], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/build_info/BUILD b/tensorflow/tools/build_info/BUILD index cdc47076ce..7307417805 100644 --- a/tensorflow/tools/build_info/BUILD +++ b/tensorflow/tools/build_info/BUILD @@ -9,18 +9,3 @@ exports_files( "gen_build_info.py", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/common/BUILD b/tensorflow/tools/common/BUILD index 316e5469e7..b9032c046e 100644 --- a/tensorflow/tools/common/BUILD +++ b/tensorflow/tools/common/BUILD @@ -44,14 +44,3 @@ py_test( "//tensorflow/python:platform_test", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/tools/compatibility/BUILD b/tensorflow/tools/compatibility/BUILD index 4f90c4d940..b7bfb29aae 100644 --- a/tensorflow/tools/compatibility/BUILD +++ b/tensorflow/tools/compatibility/BUILD @@ -68,18 +68,3 @@ exports_files( "testdata/test_file_v0_11.py", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/dist_test/server/BUILD b/tensorflow/tools/dist_test/server/BUILD index 865af8dd7b..003a19a9ab 100644 --- a/tensorflow/tools/dist_test/server/BUILD +++ b/tensorflow/tools/dist_test/server/BUILD @@ -37,15 +37,3 @@ py_test( "//tensorflow/python:client_testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/docker/BUILD b/tensorflow/tools/docker/BUILD index 7d5ae0a94d..849ba49f71 100644 --- a/tensorflow/tools/docker/BUILD +++ b/tensorflow/tools/docker/BUILD @@ -13,15 +13,3 @@ py_binary( srcs_version = "PY2AND3", deps = ["//tensorflow:tensorflow_py"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/docker/notebooks/BUILD b/tensorflow/tools/docker/notebooks/BUILD index 89f473df4b..e9f26899c9 100644 --- a/tensorflow/tools/docker/notebooks/BUILD +++ b/tensorflow/tools/docker/notebooks/BUILD @@ -3,15 +3,3 @@ package(default_visibility = ["//visibility:private"]) licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/docs/BUILD b/tensorflow/tools/docs/BUILD index 8f10bc9e0c..d370fbd246 100644 --- a/tensorflow/tools/docs/BUILD +++ b/tensorflow/tools/docs/BUILD @@ -142,14 +142,3 @@ py_test( "//tensorflow/python:client_testlib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/tools/git/BUILD b/tensorflow/tools/git/BUILD index 942ceab85f..daa17fbd50 100644 --- a/tensorflow/tools/git/BUILD +++ b/tensorflow/tools/git/BUILD @@ -9,18 +9,3 @@ licenses(["notice"]) # Apache 2.0 exports_files( ["gen_git_source.py"], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/graph_transforms/BUILD b/tensorflow/tools/graph_transforms/BUILD index 6e21aa2846..1ad1895269 100644 --- a/tensorflow/tools/graph_transforms/BUILD +++ b/tensorflow/tools/graph_transforms/BUILD @@ -313,14 +313,3 @@ tf_py_test( ], main = "python/transform_graph_test.py", ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), -) diff --git a/tensorflow/tools/mlpbtxt/BUILD b/tensorflow/tools/mlpbtxt/BUILD index f9f48c6500..89c683c8c4 100644 --- a/tensorflow/tools/mlpbtxt/BUILD +++ b/tensorflow/tools/mlpbtxt/BUILD @@ -32,15 +32,3 @@ tf_cc_binary( "//tensorflow/core:op_gen_lib", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/proto_text/BUILD b/tensorflow/tools/proto_text/BUILD index 39c4aac1e8..ef7bfdd3c9 100644 --- a/tensorflow/tools/proto_text/BUILD +++ b/tensorflow/tools/proto_text/BUILD @@ -96,18 +96,3 @@ tf_cc_test( "//tensorflow/core:test_main", ], ) - -# ----------------------------------------------------------------------------- -# Google-internal targets. These must be at the end for syncrepo. - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/quantization/BUILD b/tensorflow/tools/quantization/BUILD index e99ad06a06..17443a8617 100644 --- a/tensorflow/tools/quantization/BUILD +++ b/tensorflow/tools/quantization/BUILD @@ -76,15 +76,3 @@ py_binary( "//tensorflow/python:platform", ], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/tools/test/BUILD b/tensorflow/tools/test/BUILD index 159a8c1cfb..4b2026b947 100644 --- a/tensorflow/tools/test/BUILD +++ b/tensorflow/tools/test/BUILD @@ -92,15 +92,3 @@ tf_py_logged_benchmark( name = "rnn_op_benchmark", target = "//tensorflow/python/kernel_tests:rnn_test", ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/tensorflow/user_ops/BUILD b/tensorflow/user_ops/BUILD index e8198efe2e..71443cc41e 100644 --- a/tensorflow/user_ops/BUILD +++ b/tensorflow/user_ops/BUILD @@ -50,15 +50,3 @@ tf_py_test( additional_deps = ["//tensorflow:tensorflow_py"], data = [":invalid_op.so"], ) - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/third_party/hadoop/BUILD b/third_party/hadoop/BUILD index 9e98154400..c3c5e428be 100644 --- a/third_party/hadoop/BUILD +++ b/third_party/hadoop/BUILD @@ -4,18 +4,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE.txt"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - cc_library( name = "hdfs", hdrs = ["hdfs.h"], diff --git a/third_party/mpi/BUILD b/third_party/mpi/BUILD index ff3f437e92..1d6ac2fceb 100644 --- a/third_party/mpi/BUILD +++ b/third_party/mpi/BUILD @@ -1,17 +1,5 @@ licenses(["restricted"]) -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) - load("//third_party/mpi:mpi.bzl", "mpi_hdr") load("//third_party/mpi:mpi.bzl", "if_mpi") diff --git a/third_party/sycl/BUILD b/third_party/sycl/BUILD index fbdf19f205..f631b6df06 100644 --- a/third_party/sycl/BUILD +++ b/third_party/sycl/BUILD @@ -1,15 +1,3 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) diff --git a/third_party/sycl/sycl/BUILD b/third_party/sycl/sycl/BUILD index bc1d18b7b5..b045609954 100644 --- a/third_party/sycl/sycl/BUILD +++ b/third_party/sycl/sycl/BUILD @@ -5,15 +5,3 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 - -filegroup( - name = "all_files", - srcs = glob( - ["**/*"], - exclude = [ - "**/METADATA", - "**/OWNERS", - ], - ), - visibility = ["//tensorflow:__subpackages__"], -) -- GitLab From 3f7adc710495e1160acd956c482779247ef1f101 Mon Sep 17 00:00:00 2001 From: Igor Ganichev Date: Wed, 28 Mar 2018 20:51:01 -0700 Subject: [PATCH 395/906] Support structured source in GradientTape.gradient Before this change, it was easy to forget [] around the source tensor. This mistake lead to GradientTape.gradient(), returning a list of Nones. Nones normally tell to the user that the source and the target are not connected via differentiable operations, which is not the source of the error in this case. Instead of adding a check that `sources` is a list of tensors, this CL adds ability to handle structured source (which includes a lone tensor), similarly to many existing TensorFlow APIs. Also, with Alex's help, it fixes a bug where repeated tensors in `sources` were not handled correctly. PiperOrigin-RevId: 190878583 --- tensorflow/c/eager/tape.h | 21 ++++++---- tensorflow/python/eager/backprop.py | 33 ++++++++++------ tensorflow/python/eager/backprop_test.py | 47 +++++++++++++++++++++++ tensorflow/python/eager/pywrap_tfe_src.cc | 4 ++ 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/tensorflow/c/eager/tape.h b/tensorflow/c/eager/tape.h index c7bd3bdafd..97c323b872 100644 --- a/tensorflow/c/eager/tape.h +++ b/tensorflow/c/eager/tape.h @@ -601,23 +601,28 @@ Status GradientTape::ComputeGradient( } CHECK(state.op_tape.empty()); result->reserve(source_tensor_ids.size()); + gtl::FlatSet used_gradient_ids(source_tensor_ids.size()); for (auto is : source_tensor_ids) { auto grad_it = gradients.find(is); if (grad_it == gradients.end()) { result->push_back(nullptr); } else { - if (grad_it->second.size() == 1) { - result->push_back(grad_it->second[0]); - } else { - result->push_back(vspace.AggregateGradients(grad_it->second)); + if (grad_it->second.size() > 1) { + Gradient* grad = vspace.AggregateGradients(grad_it->second); + grad_it->second.clear(); + grad_it->second.push_back(grad); } - gradients.erase(grad_it); + result->push_back(grad_it->second[0]); + used_gradient_ids.insert(is); } } - VLOG(1) << "Final gradients size: " << gradients.size(); + VLOG(1) << "Final gradients size: " + << gradients.size() - used_gradient_ids.size(); for (auto grad_pair : gradients) { - for (const auto& g : grad_pair.second) { - vspace.DeleteGradient(g); + if (used_gradient_ids.find(grad_pair.first) == used_gradient_ids.end()) { + for (const auto& g : grad_pair.second) { + vspace.DeleteGradient(g); + } } } return Status::OK(); diff --git a/tensorflow/python/eager/backprop.py b/tensorflow/python/eager/backprop.py index c54a5a1445..209b012621 100644 --- a/tensorflow/python/eager/backprop.py +++ b/tensorflow/python/eager/backprop.py @@ -646,6 +646,13 @@ _default_vspace = imperative_grad.VSpace( ones=_ones) +def _handle_or_self(x): + """If x is ResourceVariable, return its handle, else x.""" + if isinstance(x, resource_variable_ops.ResourceVariable): + x = x.handle + return x + + @tf_export("GradientTape") class GradientTape(object): """Record operations for automatic differentiation. @@ -723,9 +730,7 @@ class GradientTape(object): tensor: a Tensor or list of Tensors. """ for t in nest.flatten(tensor): - if isinstance(t, resource_variable_ops.ResourceVariable): - t = t.handle - tape.watch(t) + tape.watch(_handle_or_self(t)) def watched_variables(self): # Sorting variables by id, which is monotonically increasing in construction @@ -739,14 +744,15 @@ class GradientTape(object): Args: target: Tensor to be differentiated. - sources: a list of Tensors or Variables. `target` will be differentiated - against elements in `sources`. + sources: a list or nested structure of Tensors or Variables. `target` + will be differentiated against elements in `sources`. output_gradients: a list of gradients, one for each element of target. Defaults to None. Returns: - a list of Tensors (or IndexedSlices, or None), one for each element in - `sources`. + a list or nested structure of Tensors (or IndexedSlices, or None), + one for each element in `sources`. Returned structure is the same as + the structure of `sources`. Raises: RuntimeError: if called inside the context of the tape, or if called more @@ -756,12 +762,15 @@ class GradientTape(object): raise RuntimeError("GradientTape.gradient can only be called once " "on non-persistent tapes, and " "only when the context manager has exited.") - sources = [x.handle if isinstance(x, resource_variable_ops.ResourceVariable) - else x - for x in sources] - grad = imperative_grad.imperative_grad( - _default_vspace, self._tape, [target], sources, + flat_sources = nest.flatten(sources) + flat_sources = [_handle_or_self(x) for x in flat_sources] + + flat_grad = imperative_grad.imperative_grad( + _default_vspace, self._tape, [target], flat_sources, output_gradients=output_gradients) + if not self._persistent: self._tape = None + + grad = nest.pack_sequence_as(sources, flat_grad) return grad diff --git a/tensorflow/python/eager/backprop_test.py b/tensorflow/python/eager/backprop_test.py index f04d89a6d9..991b4dbe7a 100644 --- a/tensorflow/python/eager/backprop_test.py +++ b/tensorflow/python/eager/backprop_test.py @@ -369,6 +369,53 @@ class BackpropTest(test.TestCase): self.assertEqual(backprop.implicit_grad(f)()[0][0], None) + @test_util.assert_no_new_tensors + @test_util.run_in_graph_and_eager_modes() + def testGradientTapeRepeatedSource(self): + with backprop.GradientTape(persistent=False) as g: + x = constant_op.constant(3.0) + g.watch(x) + y = 2 * x + grad = g.gradient(target=y, sources=[x, x]) + self.assertEqual(self.evaluate(grad), [2.0, 2.0]) + + @test_util.assert_no_new_tensors + @test_util.run_in_graph_and_eager_modes() + def testPersistentGradientTapeRepeatedSource(self): + with backprop.GradientTape(persistent=True) as g: + x = constant_op.constant(3.0) + y = constant_op.constant(5.0) + g.watch(x) + g.watch(y) + z = x * x + x * y + grad = g.gradient(target=z, sources=[x, x]) + self.assertEqual(self.evaluate(grad), [11.0, 11.0]) + grad = g.gradient(target=z, sources=[y, x]) + self.assertEqual(self.evaluate(grad), [3.0, 11.0]) + + @test_util.assert_no_new_tensors + @test_util.run_in_graph_and_eager_modes() + def testGradientTapeStructure(self): + with backprop.GradientTape(persistent=True) as g: + # Using different constant values because constant tensors are + # cached, leading to a different gradient then what one might expect. + x1 = constant_op.constant(3.0) + x2 = constant_op.constant(3.1) + x3 = constant_op.constant(3.2) + g.watch(x1) + g.watch(x2) + g.watch(x3) + y = x1 + 2 * x2 + 3 * x3 + self.assertEqual(self.evaluate(g.gradient(y, x1)), [1.0]) + self.assertEqual(self.evaluate(g.gradient(y, (x1,))), (1.0,)) + self.assertEqual(self.evaluate(g.gradient(y, (x1, x2))), (1.0, 2.0)) + self.assertEqual(self.evaluate(g.gradient(y, [(x1, x2), (x2, x3)])), + [(1.0, 2.0), (2.0, 3.0)]) + self.assertEqual(self.evaluate(g.gradient(y, (x1, x2, [x1, x3]))), + (1.0, 2.0, [1.0, 3.0])) + self.assertEqual(self.evaluate(g.gradient(y, [x1, {'x2': x2, 'x3': x3}])), + [1.0, {'x2': 2.0, 'x3': 3.0}]) + @test_util.assert_no_new_tensors @test_util.run_in_graph_and_eager_modes() def testGradientTape(self): diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc index 73482792d5..8a398f6447 100644 --- a/tensorflow/python/eager/pywrap_tfe_src.cc +++ b/tensorflow/python/eager/pywrap_tfe_src.cc @@ -1372,11 +1372,15 @@ PyObject* TFE_Py_TapeGradient(PyObject* tape, PyObject* vspace, } if (!result.empty()) { PyObject* py_result = PyList_New(result.size()); + tensorflow::gtl::FlatSet seen_results(result.size()); for (int i = 0; i < result.size(); ++i) { if (result[i] == nullptr) { Py_INCREF(Py_None); result[i] = Py_None; + } else if (seen_results.find(result[i]) != seen_results.end()) { + Py_INCREF(result[i]); } + seen_results.insert(result[i]); PyList_SET_ITEM(py_result, i, reinterpret_cast(result[i])); } return py_result; -- GitLab From 5bc7c510fd99dd6f887eb2c5834ae8297891dea7 Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Wed, 28 Mar 2018 21:07:02 -0700 Subject: [PATCH 396/906] Fixed the shape function of the SplitV op that incorrectly often assumed that the shape of all the outputs is the same. PiperOrigin-RevId: 190879600 --- .../python/kernel_tests/shape_ops_test.py | 5 +---- .../contrib/signal/python/ops/shape_ops.py | 2 -- tensorflow/core/ops/array_ops.cc | 17 +++++++++-------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py index bc4663fbb0..64cc8c7ea5 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py @@ -338,10 +338,7 @@ class FrameTest(test.TestCase): def test_constant_folding(self): """frame should be constant foldable for constant inputs.""" - # Padding is incorrectly defined in shape_ops.py (the rank of the padding - # tensor should be equal to the rank of the input tensor + 1): only test - # with padding set to False to avoid this. - for pad_end in [False]: + for pad_end in [True, False]: g = ops.Graph() with g.as_default(): frame_length, frame_step = 32, 16 diff --git a/tensorflow/contrib/signal/python/ops/shape_ops.py b/tensorflow/contrib/signal/python/ops/shape_ops.py index 97fe20866b..1ddc2941ec 100644 --- a/tensorflow/contrib/signal/python/ops/shape_ops.py +++ b/tensorflow/contrib/signal/python/ops/shape_ops.py @@ -139,8 +139,6 @@ def frame(signal, frame_length, frame_step, pad_end=False, pad_value=0, axis=-1, [[0, pad_samples]], array_ops.zeros([num_inner_dimensions, 2], dtype=pad_samples.dtype)], 0) - # TODO(rjryan): the paddings tensor must of rank tf.rank(signal) + 1. This - # isn't the case here and should be fixed. signal = array_ops.pad(signal, paddings, constant_values=pad_value) signal_shape = array_ops.shape(signal) diff --git a/tensorflow/core/ops/array_ops.cc b/tensorflow/core/ops/array_ops.cc index 88d2aa3f41..af8afc90f5 100644 --- a/tensorflow/core/ops/array_ops.cc +++ b/tensorflow/core/ops/array_ops.cc @@ -494,9 +494,9 @@ REGISTER_OP("SplitV") const Tensor* size_splits = c->input_tensor(1); if (rank == InferenceContext::kUnknownRank) { // If the rank of input tensor is unknown, then return unknown shapes. - output_shape = c->UnknownShape(); + // Note that the shape of each output can be different. for (int i = 0; i < num_outputs; ++i) { - c->set_output(i, output_shape); + c->set_output(i, c->UnknownShape()); } } else if (rank == 0) { // Throw error if input is a scalar. @@ -505,18 +505,19 @@ REGISTER_OP("SplitV") // If split dimension is known, but the sizes are unknown, then // only the split dimension is unknown output_shape = input; - TF_RETURN_IF_ERROR(c->ReplaceDim(output_shape, - c->Value(split_dimension), - c->UnknownDim(), &output_shape)); for (int i = 0; i < num_outputs; ++i) { + TF_RETURN_IF_ERROR(c->ReplaceDim(output_shape, + c->Value(split_dimension), + c->UnknownDim(), &output_shape)); c->set_output(i, output_shape); } } else if (size_splits == nullptr && !c->ValueKnown(split_dimension)) { // If split dimension or tensor containing the split sizes is unknown, - // then return unknown shapes of same rank as input. - output_shape = c->UnknownShapeOfRank(rank); + // then return unknown shapes of same rank as input. Note that each + // output shape can be different since splitv doesn't always split + // tensors evenly. for (int i = 0; i < num_outputs; ++i) { - c->set_output(i, output_shape); + c->set_output(i, c->UnknownShapeOfRank(rank)); } } else { // Determine the output shape if split dimension and split sizes are -- GitLab From aeaec465f2f08e32c524e23fb7b0ac016f3dc6a9 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 21:11:16 -0700 Subject: [PATCH 397/906] Fix TensorList decoding bug. Thanks to Alexandre Passos for finding this. PiperOrigin-RevId: 190879840 --- tensorflow/core/kernels/list_kernels.cc | 1 + tensorflow/core/kernels/list_kernels.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/list_kernels.cc b/tensorflow/core/kernels/list_kernels.cc index baf0a4abe4..9e7786f25e 100644 --- a/tensorflow/core/kernels/list_kernels.cc +++ b/tensorflow/core/kernels/list_kernels.cc @@ -112,6 +112,7 @@ bool TensorList::Decode(const VariantTensorData& data) { dims.push_back(scratch); } } + element_shape = PartialTensorShape(dims); return true; } diff --git a/tensorflow/core/kernels/list_kernels.h b/tensorflow/core/kernels/list_kernels.h index 9733883001..8af48f0a67 100644 --- a/tensorflow/core/kernels/list_kernels.h +++ b/tensorflow/core/kernels/list_kernels.h @@ -83,7 +83,8 @@ class TensorListStack : public OpKernel { DataTypeString(l->element_dtype))); OP_REQUIRES(c, l->element_shape.IsFullyDefined(), errors::InvalidArgument("Tried to stack elements from a list " - "with non-fully-defined shape.")); + "with non-fully-defined shape: ", + l->element_shape.DebugString())); if (num_elements_ != -1) { OP_REQUIRES(c, l->tensors.size() == num_elements_, errors::InvalidArgument("Operation expected a list with ", -- GitLab From 163bf8d0620a08d186c1315b0789e898f09759f8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 28 Mar 2018 21:52:30 -0700 Subject: [PATCH 398/906] DistributionStrategy-enable Estimator. PiperOrigin-RevId: 190882152 --- .../python/learn/estimators/run_config.py | 11 +- tensorflow/python/estimator/estimator.py | 341 +++++++++++++----- tensorflow/python/estimator/run_config.py | 3 +- 3 files changed, 271 insertions(+), 84 deletions(-) diff --git a/tensorflow/contrib/learn/python/learn/estimators/run_config.py b/tensorflow/contrib/learn/python/learn/estimators/run_config.py index 1d161093de..f3500bf56f 100644 --- a/tensorflow/contrib/learn/python/learn/estimators/run_config.py +++ b/tensorflow/contrib/learn/python/learn/estimators/run_config.py @@ -290,8 +290,15 @@ class RunConfig(ClusterConfig, core_run_config.RunConfig): Note - using this argument, it is easy to provide settings which break otherwise perfectly good models. Use with care. """ - super(RunConfig, self).__init__( - master=master, evaluation_master=evaluation_master) + # Neither parent class calls super().__init__(), so here we have to + # manually call their __init__() methods. + ClusterConfig.__init__( + self, master=master, evaluation_master=evaluation_master) + # For too long this code didn't call: + # core_run_config.RunConfig.__init__(self) + # so instead of breaking compatibility with that assumption, we + # just manually initialize this field: + self._distribute = None gpu_options = config_pb2.GPUOptions( per_process_gpu_memory_fraction=gpu_memory_fraction) diff --git a/tensorflow/python/estimator/estimator.py b/tensorflow/python/estimator/estimator.py index 6a4132bca2..2fe521b063 100644 --- a/tensorflow/python/estimator/estimator.py +++ b/tensorflow/python/estimator/estimator.py @@ -41,8 +41,11 @@ from tensorflow.python.estimator.export.export import get_temp_export_dir from tensorflow.python.estimator.export.export import get_timestamped_export_dir from tensorflow.python.framework import ops from tensorflow.python.framework import random_seed +from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import metrics as metrics_lib +from tensorflow.python.ops import resources +from tensorflow.python.ops import variables from tensorflow.python.platform import gfile from tensorflow.python.platform import tf_logging as logging from tensorflow.python.saved_model import builder as saved_model_builder @@ -50,6 +53,7 @@ from tensorflow.python.saved_model import tag_constants from tensorflow.python.summary import summary from tensorflow.python.summary.writer import writer_cache from tensorflow.python.training import device_setter +from tensorflow.python.training import distribute as distribute_lib from tensorflow.python.training import evaluation from tensorflow.python.training import monitored_session from tensorflow.python.training import saver @@ -183,6 +187,9 @@ class Estimator(object): config) self._config = config + # The distribute field contains an instance of DistributionStrategy. + self._distribution = self._config.distribute + # Model directory. model_dir = compat_internal.path_to_str(model_dir) if (model_dir is not None) and (self._config.model_dir is not None): @@ -682,11 +689,25 @@ class Estimator(object): def _get_features_and_labels_from_input_fn(self, input_fn, mode): """Extracts the `features` and labels from return values of `input_fn`.""" result = self._call_input_fn(input_fn, mode) + # TODO(anjalisridhar): What about the default DistributionStrategy? Perhaps + # using any input is alright in that case. There is also a + # has_dataset_or_queue_runner function that we may want to extend and use. + if (self._distribution is not None and + not isinstance(result, dataset_ops.Dataset)): + raise ValueError('input_fn() must return a tf.data.Dataset when using a ' + 'DistributionStrategy.') input_hooks = [] if isinstance(result, dataset_ops.Dataset): - iterator = result.make_initializable_iterator() - input_hooks.append(_DatasetInitializerHook(iterator)) - result = iterator.get_next() + if self._distribution is not None and mode == model_fn_lib.ModeKeys.TRAIN: + # TODO(josh11b): This is currently using a one-shot iterator, we + # will update this to an initializeable iterator once the + # necessory support for creating an initializable iterator is + # available. + result = self._distribution.distribute_dataset(result).get_next() + else: + iterator = result.make_initializable_iterator() + input_hooks.append(_DatasetInitializerHook(iterator)) + result = iterator.get_next() if isinstance(result, (list, tuple)): if len(result) != 2: raise ValueError( @@ -815,6 +836,12 @@ class Estimator(object): return model_fn_results def _train_model(self, input_fn, hooks, saving_listeners): + if self._distribution: + return self._train_model_distributed(input_fn, hooks, saving_listeners) + else: + return self._train_model_default(input_fn, hooks, saving_listeners) + + def _train_model_default(self, input_fn, hooks, saving_listeners): worker_hooks = [] with ops.Graph().as_default() as g, g.device(self._device_fn): random_seed.set_random_seed(self._config.tf_random_seed) @@ -826,86 +853,209 @@ class Estimator(object): worker_hooks.extend(input_hooks) estimator_spec = self._call_model_fn( features, labels, model_fn_lib.ModeKeys.TRAIN, self.config) + return self._train_with_estimator_spec(estimator_spec, worker_hooks, + hooks, global_step_tensor, + saving_listeners) - if self._warm_start_settings: - logging.info('Warm-starting with WarmStartSettings: %s' % - (self._warm_start_settings,)) - # pylint: disable=protected-access - warm_starting_util.warm_start(*self._warm_start_settings) - # pylint: enable=protected-access - # Check if the user created a loss summary, and add one if they didn't. - # We assume here that the summary is called 'loss'. If it is not, we will - # make another one with the name 'loss' to ensure it shows up in the right - # graph in TensorBoard. - if not any([x.op.name == 'loss' - for x in ops.get_collection(ops.GraphKeys.SUMMARIES)]): - summary.scalar('loss', estimator_spec.loss) - ops.add_to_collection(ops.GraphKeys.LOSSES, estimator_spec.loss) - worker_hooks.extend(hooks) - worker_hooks.extend([ - training.NanTensorHook(estimator_spec.loss), - training.LoggingTensorHook( - { - 'loss': estimator_spec.loss, - 'step': global_step_tensor - }, - every_n_iter=self._config.log_step_count_steps) - ]) - worker_hooks.extend(estimator_spec.training_hooks) - - if not (estimator_spec.scaffold.saver or - ops.get_collection(ops.GraphKeys.SAVERS)): - ops.add_to_collection( - ops.GraphKeys.SAVERS, - training.Saver( - sharded=True, - max_to_keep=self._config.keep_checkpoint_max, - keep_checkpoint_every_n_hours=( - self._config.keep_checkpoint_every_n_hours), - defer_build=True, - save_relative_paths=True)) - - chief_hooks = [] - all_hooks = worker_hooks + list(estimator_spec.training_chief_hooks) - saver_hooks = [ - h for h in all_hooks if isinstance(h, training.CheckpointSaverHook)] - if (self._config.save_checkpoints_secs or - self._config.save_checkpoints_steps): - if not saver_hooks: - chief_hooks = [ - training.CheckpointSaverHook( - self._model_dir, - save_secs=self._config.save_checkpoints_secs, - save_steps=self._config.save_checkpoints_steps, - scaffold=estimator_spec.scaffold) - ] - saver_hooks = [chief_hooks[0]] - if saving_listeners: - if not saver_hooks: - raise ValueError( - 'There should be a CheckpointSaverHook to use saving_listeners. ' - 'Please set one of the RunConfig.save_checkpoints_steps or ' - 'RunConfig.save_checkpoints_secs.') + def _train_model_distributed(self, input_fn, hooks, saving_listeners): + worker_hooks = [] + with ops.Graph().as_default() as g: + with self._distribution.scope(): + random_seed.set_random_seed(self._config.tf_random_seed) + features, labels, input_hooks = ( + self._get_features_and_labels_from_input_fn( + input_fn, model_fn_lib.ModeKeys.TRAIN)) + worker_hooks.extend(input_hooks) + global_step_tensor = self._create_and_assert_global_step(g) + # The default destination for the global_step_tensor fetch call is the + # CPU. + global_step_read_tensor = self._distribution.fetch(global_step_tensor) + # we want to add to the global collection in the main thread not the + # tower threads. + ops.add_to_collection(training_util.GLOBAL_STEP_READ_KEY, + global_step_read_tensor) + grouped_estimator_spec = self._distribution.call_for_each_tower( + self._call_model_fn, + features, + labels, # although this will be None it seems + model_fn_lib.ModeKeys.TRAIN, + self.config) + + # TODO(anjalisridhar): Figure out how to resolve the folowing scaffold + # parameters: init_feed_dict, init_fn. + scaffold_list = self._distribution.unwrap( + grouped_estimator_spec.scaffold) + init_feed_dict = [ + s.init_feed_dict + for s in scaffold_list + if s.init_feed_dict is not None + ] + if init_feed_dict: + init_feed_dict = self._distribution.group(init_feed_dict) else: - # It is expected to have one CheckpointSaverHook. If multiple, we pick - # up the first one to add listener. - saver_hooks[0]._listeners.extend(saving_listeners) # pylint: disable=protected-access - with training.MonitoredTrainingSession( - master=self._config.master, - is_chief=self._config.is_chief, - checkpoint_dir=self._model_dir, - scaffold=estimator_spec.scaffold, - hooks=worker_hooks, - chief_only_hooks=( - tuple(chief_hooks) + tuple(estimator_spec.training_chief_hooks)), - save_checkpoint_secs=0, # Saving is handled by a hook. - save_summaries_steps=self._config.save_summary_steps, - config=self._session_config, - log_step_count_steps=self._config.log_step_count_steps) as mon_sess: - loss = None - while not mon_sess.should_stop(): - _, loss = mon_sess.run([estimator_spec.train_op, estimator_spec.loss]) - return loss + init_feed_dict = None + + init_fn = [s.init_fn for s in scaffold_list if s.init_fn is not None] + if init_fn: + init_fn = self._distribution.group(init_fn) + else: + init_fn = None + + init_op = [s.init_op for s in scaffold_list if s.init_op is not None] + if init_op: + init_op = self._distribution.group(init_op) + else: + init_op = None + + ready_op = self._distribution.call_for_each_tower( + create_per_tower_ready_op, grouped_estimator_spec.scaffold) + if ready_op is not None: + ready_op = self._distribution.group(ready_op) + else: + ready_op = None + + ready_for_local_init_op = self._distribution.call_for_each_tower( + create_per_tower_ready_for_local_init_op, + grouped_estimator_spec.scaffold) + if ready_for_local_init_op is not None: + ready_for_local_init_op = self._distribution.group( + ready_for_local_init_op) + else: + ready_for_local_init_op = None + + local_init_op = [ + s.local_init_op + for s in scaffold_list + if s.local_init_op is not None + ] + if local_init_op: + local_init_op = self._distribution.group(local_init_op) + else: + local_init_op = None + + summary_op = [ + s.summary_op for s in scaffold_list if s.summary_op is not None + ] + if summary_op: + summary_op = self._distribution.group(summary_op) + else: + summary_op = None + + scaffold = monitored_session.Scaffold( + init_op=init_op, + ready_op=ready_op, + ready_for_local_init_op=ready_for_local_init_op, + local_init_op=local_init_op, + summary_op=summary_op, + init_feed_dict=init_feed_dict, + init_fn=init_fn) + + def get_hooks_from_the_first_device(per_device_hooks): + hooks_list = self._distribution.unwrap(per_device_hooks) + assert hooks_list + return hooks_list[0] + + training_hooks = get_hooks_from_the_first_device( + grouped_estimator_spec.training_hooks) + training_chief_hooks = get_hooks_from_the_first_device( + grouped_estimator_spec.training_chief_hooks) + + estimator_spec = model_fn_lib.EstimatorSpec( + mode=grouped_estimator_spec.mode, + loss=self._distribution.unwrap( + self._distribution.reduce(distribute_lib.get_loss_reduction(), + grouped_estimator_spec.loss, + destinations='/device:CPU:0'))[0], + train_op=self._distribution.group(grouped_estimator_spec.train_op), + training_hooks=training_hooks, + training_chief_hooks=training_chief_hooks, + scaffold=scaffold) + return self._train_with_estimator_spec(estimator_spec, worker_hooks, + hooks, global_step_read_tensor, + saving_listeners) + + def _train_with_estimator_spec(self, estimator_spec, worker_hooks, hooks, + global_step_tensor, saving_listeners): + """Train a model with the given Estimator Spec.""" + if self._warm_start_settings: + logging.info('Warm-starting with WarmStartSettings: %s' % + (self._warm_start_settings,)) + # pylint: disable=protected-access + warm_starting_util.warm_start(*self._warm_start_settings) + # pylint: enable=protected-access + # Check if the user created a loss summary, and add one if they didn't. + # We assume here that the summary is called 'loss'. If it is not, we will + # make another one with the name 'loss' to ensure it shows up in the right + # graph in TensorBoard. + if not any([x.op.name == 'loss' + for x in ops.get_collection(ops.GraphKeys.SUMMARIES)]): + summary.scalar('loss', estimator_spec.loss) + ops.add_to_collection(ops.GraphKeys.LOSSES, estimator_spec.loss) + worker_hooks.extend(hooks) + worker_hooks.extend([ + training.NanTensorHook(estimator_spec.loss), + training.LoggingTensorHook( + { + 'loss': estimator_spec.loss, + 'step': global_step_tensor + }, + every_n_iter=self._config.log_step_count_steps) + ]) + worker_hooks.extend(estimator_spec.training_hooks) + + if not (estimator_spec.scaffold.saver or + ops.get_collection(ops.GraphKeys.SAVERS)): + ops.add_to_collection( + ops.GraphKeys.SAVERS, + training.Saver( + sharded=True, + max_to_keep=self._config.keep_checkpoint_max, + keep_checkpoint_every_n_hours=( + self._config.keep_checkpoint_every_n_hours), + defer_build=True, + save_relative_paths=True)) + + chief_hooks = [] + all_hooks = worker_hooks + list(estimator_spec.training_chief_hooks) + saver_hooks = [ + h for h in all_hooks if isinstance(h, training.CheckpointSaverHook)] + if (self._config.save_checkpoints_secs or + self._config.save_checkpoints_steps): + if not saver_hooks: + chief_hooks = [ + training.CheckpointSaverHook( + self._model_dir, + save_secs=self._config.save_checkpoints_secs, + save_steps=self._config.save_checkpoints_steps, + scaffold=estimator_spec.scaffold) + ] + saver_hooks = [chief_hooks[0]] + if saving_listeners: + if not saver_hooks: + raise ValueError( + 'There should be a CheckpointSaverHook to use saving_listeners. ' + 'Please set one of the RunConfig.save_checkpoints_steps or ' + 'RunConfig.save_checkpoints_secs.') + else: + # It is expected to have one CheckpointSaverHook. If multiple, we pick + # up the first one to add listener. + saver_hooks[0]._listeners.extend(saving_listeners) # pylint: disable=protected-access + with training.MonitoredTrainingSession( + master=self._config.master, + is_chief=self._config.is_chief, + checkpoint_dir=self._model_dir, + scaffold=estimator_spec.scaffold, + hooks=worker_hooks, + chief_only_hooks=( + tuple(chief_hooks) + tuple(estimator_spec.training_chief_hooks)), + save_checkpoint_secs=0, # Saving is handled by a hook. + save_summaries_steps=self._config.save_summary_steps, + config=self._session_config, + log_step_count_steps=self._config.log_step_count_steps) as mon_sess: + loss = None + while not mon_sess.should_stop(): + _, loss = mon_sess.run([estimator_spec.train_op, estimator_spec.loss]) + return loss def _evaluate_model(self, input_fn, @@ -972,6 +1122,35 @@ class Estimator(object): return eval_results +def create_per_tower_ready_op(scaffold): + """Create a Scaffold.ready_op inside a tower.""" + if scaffold.ready_op: + return scaffold.ready_op + + def default_ready_op(): + return array_ops.concat([ + variables.report_uninitialized_variables(), + resources.report_uninitialized_resources() + ], 0) + + return monitored_session.Scaffold.get_or_default( + 'ready_op', ops.GraphKeys.READY_OP, default_ready_op) + + +def create_per_tower_ready_for_local_init_op(scaffold): + """Create a Scaffold.ready_for_local_init_op inside a tower.""" + if scaffold.ready_for_local_init_op: + return scaffold.ready_for_local_init_op + + def default_ready_for_local_init_op(): + return variables.report_uninitialized_variables( + variables.global_variables()) + + return monitored_session.Scaffold.get_or_default( + 'ready_for_local_init_op', ops.GraphKeys.READY_FOR_LOCAL_INIT_OP, + default_ready_for_local_init_op) + + def _check_checkpoint_available(model_dir): latest_path = saver.latest_checkpoint(model_dir) if not latest_path: diff --git a/tensorflow/python/estimator/run_config.py b/tensorflow/python/estimator/run_config.py index 141eaeff64..41415b89e9 100644 --- a/tensorflow/python/estimator/run_config.py +++ b/tensorflow/python/estimator/run_config.py @@ -688,7 +688,7 @@ class RunConfig(object): Only the properties in the following list are allowed to be replaced: - - `model_dir`. + - `model_dir`, - `tf_random_seed`, - `save_summary_steps`, - `save_checkpoints_steps`, @@ -697,6 +697,7 @@ class RunConfig(object): - `keep_checkpoint_max`, - `keep_checkpoint_every_n_hours`, - `log_step_count_steps`, + - `distribute`. In addition, either `save_checkpoints_steps` or `save_checkpoints_secs` can be set (should not be both). -- GitLab From 695aa649da752315596934319dd601854495dec5 Mon Sep 17 00:00:00 2001 From: Michael Case Date: Wed, 28 Mar 2018 22:46:25 -0700 Subject: [PATCH 399/906] Add --announce_rc Bazel arg to several of our builds. This will help to... - Refactor the build scripts without accidently adding functional changes. - Help debug several issues where some options aren't being added correctly by configure script. PiperOrigin-RevId: 190884531 --- tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh | 1 + tensorflow/tools/ci_build/osx/cpu/run_py3_cc_core.sh | 1 + tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh b/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh index 338066131b..c7cc16e669 100755 --- a/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh +++ b/tensorflow/tools/ci_build/osx/cpu/run_py2_cc_core.sh @@ -33,6 +33,7 @@ yes "" | $PYTHON_BIN_PATH configure.py which bazel bazel test --test_tag_filters=-no_oss,-gpu,-benchmark-test,-nomac,-no_mac \ --test_timeout 300,450,1200,3600 --config=opt \ + --announce_rc \ --test_size_filters=small,medium \ --jobs=${N_JOBS} --build_tests_only --test_output=errors -k -- \ //tensorflow/... -//tensorflow/compiler/... -//tensorflow/contrib/... diff --git a/tensorflow/tools/ci_build/osx/cpu/run_py3_cc_core.sh b/tensorflow/tools/ci_build/osx/cpu/run_py3_cc_core.sh index 920a261ae3..7e0e81a1eb 100755 --- a/tensorflow/tools/ci_build/osx/cpu/run_py3_cc_core.sh +++ b/tensorflow/tools/ci_build/osx/cpu/run_py3_cc_core.sh @@ -31,6 +31,7 @@ export PYTHON_BIN_PATH=$(which python3) yes "" | $PYTHON_BIN_PATH configure.py which bazel bazel test --test_tag_filters=-no_oss,-gpu,-benchmark-test,-nomac,-no_mac \ + --announce_rc \ --test_timeout 300,450,1200,3600 \ --test_size_filters=small,medium \ --jobs=${N_JOBS} --build_tests_only --test_output=errors -k -- \ diff --git a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh index 94276c6c5c..7dfee8f371 100644 --- a/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh +++ b/tensorflow/tools/ci_build/windows/libtensorflow_gpu.sh @@ -41,7 +41,7 @@ run_configure_for_gpu_build # build_libtensorflow_tarball in ../builds/libtensorflow.sh # cannot be used on Windows since it relies on pkg_tar rules. # So we do something special here -bazel build -c opt --copt=/arch:AVX \ +bazel build -c opt --copt=/arch:AVX --announce_rc \ tensorflow:libtensorflow.so \ tensorflow/tools/lib_package:clicenses_generate \ tensorflow/java:libtensorflow_jni.so \ -- GitLab From 8df77178a8d41b392928ec17e6ca4867698407ff Mon Sep 17 00:00:00 2001 From: Benoit Steiner Date: Wed, 28 Mar 2018 23:31:26 -0700 Subject: [PATCH 400/906] Move the swapping kernels to the all_kernels library to avoid registering them more than once from tensorflow/contrib. PiperOrigin-RevId: 190887394 --- tensorflow/core/BUILD | 4 ++++ tensorflow/core/grappler/optimizers/BUILD | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index d46241450c..b8dbd90ab8 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -144,6 +144,7 @@ load( "tf_cuda_tests_tags", "if_static", ) +load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda") load( "//third_party/mkl:build_defs.bzl", "if_mkl", @@ -939,6 +940,9 @@ cc_library( "//tensorflow/core/kernels:mkl_softmax_op", "//tensorflow/core/kernels:mkl_tfconv_op", "//tensorflow/core/kernels:mkl_aggregate_ops", + ]) + if_cuda([ + "//tensorflow/core/grappler/optimizers:gpu_swapping_kernels", + "//tensorflow/core/grappler/optimizers:gpu_swapping_ops", ]), ) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 0d3a488f85..2c365c467c 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -361,6 +361,7 @@ tf_kernel_library( srcs = [ "gpu_swapping_kernels.cc", ], + visibility = ["//tensorflow:__subpackages__"], deps = [ "//tensorflow/core:core_cpu_base", "//tensorflow/core:framework", @@ -373,6 +374,7 @@ cc_library( srcs = [ "gpu_swapping_ops.cc", ], + visibility = ["//tensorflow:__subpackages__"], deps = [ "//tensorflow/core:core_cpu_base", "//tensorflow/core:framework", @@ -406,10 +408,7 @@ cc_library( "//tensorflow/core/grappler/costs:graph_properties", "//tensorflow/core/grappler/utils:topological_sort", "//tensorflow/core/grappler/utils:traversal", - ] + if_cuda([ - ":gpu_swapping_kernels", - ":gpu_swapping_ops", - ]), + ], ) tf_cuda_only_cc_test( @@ -417,6 +416,8 @@ tf_cuda_only_cc_test( srcs = ["memory_optimizer_test.cc"], tags = ["no_cuda_on_cpu_tap"], # Do not re-enable again without actually testing. deps = [ + ":gpu_swapping_kernels", + ":gpu_swapping_ops", ":memory_optimizer", "//tensorflow/cc:cc_ops", "//tensorflow/core:ops", -- GitLab From fd25620e80d628d77c5e9a03e87d6a4e10eccd27 Mon Sep 17 00:00:00 2001 From: Anna R Date: Thu, 29 Mar 2018 04:34:29 -0700 Subject: [PATCH 401/906] Internal change. PiperOrigin-RevId: 190913047 --- tensorflow/contrib/eager/python/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD index 7a8c11e3bb..48372d7ae0 100644 --- a/tensorflow/contrib/eager/python/BUILD +++ b/tensorflow/contrib/eager/python/BUILD @@ -80,6 +80,7 @@ cuda_py_test( "//tensorflow/python/data", "//tensorflow/python/eager:test", ], + tags = ["noguitar"], ) py_library( -- GitLab From 93cf42ac3530d24009179c45c88a444383719c9b Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Thu, 29 Mar 2018 22:54:53 +0800 Subject: [PATCH 402/906] Fix math equation format in layers (#18069) --- tensorflow/contrib/layers/python/layers/layers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/layers.py b/tensorflow/contrib/layers/python/layers/layers.py index 350bcb3bca..10d7f6d076 100644 --- a/tensorflow/contrib/layers/python/layers/layers.py +++ b/tensorflow/contrib/layers/python/layers/layers.py @@ -3045,16 +3045,16 @@ def legacy_fully_connected(x, `activation_fn` is `None`, the result of `y = w * x + b` is returned. - If `x` has shape [\\\(\\text{dim}_0, \\text{dim}_1, ..., \\text{dim}_n\\\)] - with more than 2 dimensions (\\\(n > 1\\\)), then we repeat the matrix + If `x` has shape [\\(\text{dim}_0, \text{dim}_1, ..., \text{dim}_n\\)] + with more than 2 dimensions (\\(n > 1\\)), then we repeat the matrix multiply along the first dimensions. The result r is a tensor of shape - [\\\(\\text{dim}_0, ..., \\text{dim}_{n-1},\\\) `num_output_units`], - where \\\( r_{i_0, ..., i_{n-1}, k} = - \\sum_{0 \\leq j < \\text{dim}_n} x_{i_0, ... i_{n-1}, j} \cdot w_{j, k}\\\). + [\\(\text{dim}_0, ..., \text{dim}_{n-1},\\) `num_output_units`], + where \\( r_{i_0, ..., i_{n-1}, k} = + \sum_{0 \leq j < \text{dim}_n} x_{i_0, ... i_{n-1}, j} \cdot w_{j, k}\\). This is accomplished by reshaping `x` to 2-D - [\\\(\\text{dim}_0 \\cdot ... \\cdot \\text{dim}_{n-1}, \\text{dim}_n\\\)] + [\\(\text{dim}_0 \cdot ... \cdot \text{dim}_{n-1}, \text{dim}_n\\)] before the matrix multiply and afterwards reshaping it to - [\\\(\\text{dim}_0, ..., \\text{dim}_{n-1},\\\) `num_output_units`]. + [\\(\text{dim}_0, ..., \text{dim}_{n-1},\\) `num_output_units`]. This op creates `w` and optionally `b`. Bias (`b`) can be disabled by setting `bias_init` to `None`. -- GitLab From 481dca1987e030f9986ce16ae05142617d631641 Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Thu, 29 Mar 2018 07:55:23 -0700 Subject: [PATCH 403/906] Default disable including the coordinator in the TPU job (#18073) * Default disable including the coordinator in the TPU job * Fix the test --- .../python/training/tpu_cluster_resolver.py | 2 +- .../python/training/tpu_cluster_resolver_test.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py index 300b19733e..95c5c920aa 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py +++ b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py @@ -73,7 +73,7 @@ class TPUClusterResolver(ClusterResolver): zone=None, project=None, job_name='worker', - coordinator_name='coordinator', + coordinator_name=None, coordinator_address=None, credentials='default', service=None): diff --git a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py index 48c3f6bb4f..e1e3e6867a 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver_test.py @@ -117,7 +117,8 @@ class TPUClusterResolverTest(test.TestCase): zone=None, tpu=['test-tpu-1'], credentials=None, - service=self.mock_service_client(tpu_map=tpu_map)) + service=self.mock_service_client(tpu_map=tpu_map), + coordinator_name='coordinator') actual_cluster_spec = tpu_cluster_resolver.cluster_spec() expected_proto = """ @@ -170,6 +171,7 @@ class TPUClusterResolverTest(test.TestCase): project='test-project', zone='us-central1-c', tpu=['test-tpu-1'], + coordinator_name='coordinator', coordinator_address='10.128.1.5:10203', credentials=None, service=self.mock_service_client(tpu_map=tpu_map)) @@ -196,6 +198,7 @@ class TPUClusterResolverTest(test.TestCase): project='test-project', zone='us-central1-c', tpu='test-tpu-1', + coordinator_name='coordinator', coordinator_address='10.128.1.5:10203', credentials=None, service=self.mock_service_client(tpu_map=tpu_map)) @@ -239,7 +242,8 @@ class TPUClusterResolverTest(test.TestCase): tpu_cluster_resolver = TPUClusterResolver( tpu='test-tpu-1', credentials=None, - service=self.mock_service_client(tpu_map=tpu_map)) + service=self.mock_service_client(tpu_map=tpu_map), + coordinator_name='coordinator') actual_cluster_spec = tpu_cluster_resolver.cluster_spec() expected_proto = """ -- GitLab From 76c569a29ec33d1965757eeed1bdc317f2fb5e87 Mon Sep 17 00:00:00 2001 From: "Joshua V. Dillon" Date: Thu, 29 Mar 2018 07:56:15 -0700 Subject: [PATCH 404/906] Add meta-distribution which reshapes batch dims. PiperOrigin-RevId: 190930846 --- tensorflow/contrib/distributions/BUILD | 14 + tensorflow/contrib/distributions/__init__.py | 4 +- .../python/kernel_tests/batch_reshape_test.py | 531 ++++++++++++++++++ .../distributions/python/ops/batch_reshape.py | 333 +++++++++++ 4 files changed, 881 insertions(+), 1 deletion(-) create mode 100644 tensorflow/contrib/distributions/python/kernel_tests/batch_reshape_test.py create mode 100644 tensorflow/contrib/distributions/python/ops/batch_reshape.py diff --git a/tensorflow/contrib/distributions/BUILD b/tensorflow/contrib/distributions/BUILD index 231abaa2f3..de08eb491b 100644 --- a/tensorflow/contrib/distributions/BUILD +++ b/tensorflow/contrib/distributions/BUILD @@ -456,6 +456,20 @@ cuda_py_test( ], ) +cuda_py_test( + name = "batch_reshape_test", + size = "small", + srcs = ["python/kernel_tests/batch_reshape_test.py"], + additional_deps = [ + ":distributions_py", + "//third_party/py/numpy", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + ], +) + cuda_py_test( name = "sample_stats_test", size = "medium", diff --git a/tensorflow/contrib/distributions/__init__.py b/tensorflow/contrib/distributions/__init__.py index 61c411271d..4d4489468d 100644 --- a/tensorflow/contrib/distributions/__init__.py +++ b/tensorflow/contrib/distributions/__init__.py @@ -24,6 +24,7 @@ from __future__ import print_function from tensorflow.contrib.distributions.python.ops import bijectors from tensorflow.contrib.distributions.python.ops.autoregressive import * +from tensorflow.contrib.distributions.python.ops.batch_reshape import * from tensorflow.contrib.distributions.python.ops.binomial import * from tensorflow.contrib.distributions.python.ops.cauchy import * from tensorflow.contrib.distributions.python.ops.chi2 import * @@ -96,9 +97,10 @@ _allowed_symbols = [ 'ReparameterizationType', 'Distribution', 'Autoregressive', - 'Binomial', + 'BatchReshape', 'Bernoulli', 'Beta', + 'Binomial', 'BetaWithSoftplusConcentration', 'Categorical', 'Chi2', diff --git a/tensorflow/contrib/distributions/python/kernel_tests/batch_reshape_test.py b/tensorflow/contrib/distributions/python/kernel_tests/batch_reshape_test.py new file mode 100644 index 0000000000..4d2f40e27f --- /dev/null +++ b/tensorflow/contrib/distributions/python/kernel_tests/batch_reshape_test.py @@ -0,0 +1,531 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for BatchReshape.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.distributions.python.ops import batch_reshape as batch_reshape_lib +from tensorflow.contrib.distributions.python.ops import mvn_diag as mvn_lib +from tensorflow.contrib.distributions.python.ops import wishart as wishart_lib +from tensorflow.python.framework import constant_op +from tensorflow.python.ops import array_ops +from tensorflow.python.ops.distributions import normal as normal_lib +from tensorflow.python.platform import test + + +class _BatchReshapeTest(object): + + def make_wishart(self, dims, new_batch_shape, old_batch_shape): + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = self.dtype([ + [[1., 0.5], + [0.5, 1.]], + [[0.5, 0.25], + [0.25, 0.75]], + ]) + scale = np.reshape(np.concatenate([scale, scale], axis=0), + old_batch_shape + [dims, dims]) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + wishart = wishart_lib.WishartFull(df=5, scale=scale_ph) + reshape_wishart = batch_reshape_lib.BatchReshape( + distribution=wishart, + batch_shape=new_batch_shape_ph, + validate_args=True) + + return wishart, reshape_wishart + + def test_matrix_variate_sample_and_log_prob(self): + dims = 2 + new_batch_shape = [4] + old_batch_shape = [2, 2] + wishart, reshape_wishart = self.make_wishart( + dims, new_batch_shape, old_batch_shape) + + batch_shape = reshape_wishart.batch_shape_tensor() + event_shape = reshape_wishart.event_shape_tensor() + + expected_sample_shape = [3, 1] + new_batch_shape + [dims, dims] + x = wishart.sample([3, 1], seed=42) + expected_sample = array_ops.reshape(x, expected_sample_shape) + actual_sample = reshape_wishart.sample([3, 1], seed=42) + + expected_log_prob_shape = [3, 1] + new_batch_shape + expected_log_prob = array_ops.reshape( + wishart.log_prob(x), expected_log_prob_shape) + actual_log_prob = reshape_wishart.log_prob(expected_sample) + + with self.test_session() as sess: + [ + batch_shape_, + event_shape_, + expected_sample_, actual_sample_, + expected_log_prob_, actual_log_prob_, + ] = sess.run([ + batch_shape, + event_shape, + expected_sample, actual_sample, + expected_log_prob, actual_log_prob, + ]) + + self.assertAllEqual(new_batch_shape, batch_shape_) + self.assertAllEqual([dims, dims], event_shape_) + self.assertAllClose(expected_sample_, actual_sample_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_log_prob_, actual_log_prob_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(new_batch_shape, reshape_wishart.batch_shape) + self.assertAllEqual([dims, dims], reshape_wishart.event_shape) + self.assertAllEqual(expected_sample_shape, actual_sample.shape) + self.assertAllEqual(expected_log_prob_shape, actual_log_prob.shape) + + def test_matrix_variate_stats(self): + dims = 2 + new_batch_shape = [4] + old_batch_shape = [2, 2] + wishart, reshape_wishart = self.make_wishart( + dims, new_batch_shape, old_batch_shape) + + expected_scalar_stat_shape = new_batch_shape + expected_matrix_stat_shape = new_batch_shape + [dims, dims] + + expected_entropy = array_ops.reshape( + wishart.entropy(), expected_scalar_stat_shape) + actual_entropy = reshape_wishart.entropy() + + expected_mean = array_ops.reshape( + wishart.mean(), expected_matrix_stat_shape) + actual_mean = reshape_wishart.mean() + + expected_mode = array_ops.reshape( + wishart.mode(), expected_matrix_stat_shape) + actual_mode = reshape_wishart.mode() + + expected_stddev = array_ops.reshape( + wishart.stddev(), expected_matrix_stat_shape) + actual_stddev = reshape_wishart.stddev() + + expected_variance = array_ops.reshape( + wishart.variance(), expected_matrix_stat_shape) + actual_variance = reshape_wishart.variance() + + with self.test_session() as sess: + [ + expected_entropy_, actual_entropy_, + expected_mean_, actual_mean_, + expected_mode_, actual_mode_, + expected_stddev_, actual_stddev_, + expected_variance_, actual_variance_, + ] = sess.run([ + expected_entropy, actual_entropy, + expected_mean, actual_mean, + expected_mode, actual_mode, + expected_stddev, actual_stddev, + expected_variance, actual_variance, + ]) + + self.assertAllClose(expected_entropy_, actual_entropy_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mean_, actual_mean_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mode_, actual_mode_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_stddev_, actual_stddev_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_variance_, actual_variance_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(expected_scalar_stat_shape, actual_entropy.shape) + self.assertAllEqual(expected_matrix_stat_shape, actual_mean.shape) + self.assertAllEqual(expected_matrix_stat_shape, actual_mode.shape) + self.assertAllEqual(expected_matrix_stat_shape, actual_stddev.shape) + self.assertAllEqual(expected_matrix_stat_shape, actual_variance.shape) + + def make_normal(self, new_batch_shape, old_batch_shape): + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = self.dtype(0.5 + np.arange( + np.prod(old_batch_shape)).reshape(old_batch_shape)) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + normal = normal_lib.Normal(loc=self.dtype(0), scale=scale_ph) + reshape_normal = batch_reshape_lib.BatchReshape( + distribution=normal, + batch_shape=new_batch_shape_ph, + validate_args=True) + return normal, reshape_normal + + def test_scalar_variate_sample_and_log_prob(self): + new_batch_shape = [2, 2] + old_batch_shape = [4] + + normal, reshape_normal = self.make_normal( + new_batch_shape, old_batch_shape) + + batch_shape = reshape_normal.batch_shape_tensor() + event_shape = reshape_normal.event_shape_tensor() + + expected_sample_shape = new_batch_shape + x = normal.sample(seed=52) + expected_sample = array_ops.reshape(x, expected_sample_shape) + actual_sample = reshape_normal.sample(seed=52) + + expected_log_prob_shape = new_batch_shape + expected_log_prob = array_ops.reshape( + normal.log_prob(x), expected_log_prob_shape) + actual_log_prob = reshape_normal.log_prob(expected_sample) + + with self.test_session() as sess: + [ + batch_shape_, + event_shape_, + expected_sample_, actual_sample_, + expected_log_prob_, actual_log_prob_, + ] = sess.run([ + batch_shape, + event_shape, + expected_sample, actual_sample, + expected_log_prob, actual_log_prob, + ]) + self.assertAllEqual(new_batch_shape, batch_shape_) + self.assertAllEqual([], event_shape_) + self.assertAllClose(expected_sample_, actual_sample_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_log_prob_, actual_log_prob_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(new_batch_shape, reshape_normal.batch_shape) + self.assertAllEqual([], reshape_normal.event_shape) + self.assertAllEqual(expected_sample_shape, actual_sample.shape) + self.assertAllEqual(expected_log_prob_shape, actual_log_prob.shape) + + def test_scalar_variate_stats(self): + new_batch_shape = [2, 2] + old_batch_shape = [4] + + normal, reshape_normal = self.make_normal(new_batch_shape, old_batch_shape) + + expected_scalar_stat_shape = new_batch_shape + + expected_entropy = array_ops.reshape( + normal.entropy(), expected_scalar_stat_shape) + actual_entropy = reshape_normal.entropy() + + expected_mean = array_ops.reshape( + normal.mean(), expected_scalar_stat_shape) + actual_mean = reshape_normal.mean() + + expected_mode = array_ops.reshape( + normal.mode(), expected_scalar_stat_shape) + actual_mode = reshape_normal.mode() + + expected_stddev = array_ops.reshape( + normal.stddev(), expected_scalar_stat_shape) + actual_stddev = reshape_normal.stddev() + + expected_variance = array_ops.reshape( + normal.variance(), expected_scalar_stat_shape) + actual_variance = reshape_normal.variance() + + with self.test_session() as sess: + [ + expected_entropy_, actual_entropy_, + expected_mean_, actual_mean_, + expected_mode_, actual_mode_, + expected_stddev_, actual_stddev_, + expected_variance_, actual_variance_, + ] = sess.run([ + expected_entropy, actual_entropy, + expected_mean, actual_mean, + expected_mode, actual_mode, + expected_stddev, actual_stddev, + expected_variance, actual_variance, + ]) + self.assertAllClose(expected_entropy_, actual_entropy_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mean_, actual_mean_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mode_, actual_mode_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_stddev_, actual_stddev_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_variance_, actual_variance_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(expected_scalar_stat_shape, actual_entropy.shape) + self.assertAllEqual(expected_scalar_stat_shape, actual_mean.shape) + self.assertAllEqual(expected_scalar_stat_shape, actual_mode.shape) + self.assertAllEqual(expected_scalar_stat_shape, actual_stddev.shape) + self.assertAllEqual(expected_scalar_stat_shape, actual_variance.shape) + + def make_mvn(self, dims, new_batch_shape, old_batch_shape): + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = np.ones(old_batch_shape + [dims], self.dtype) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + mvn = mvn_lib.MultivariateNormalDiag(scale_diag=scale_ph) + reshape_mvn = batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True) + return mvn, reshape_mvn + + def test_vector_variate_sample_and_log_prob(self): + dims = 3 + new_batch_shape = [2, 1] + old_batch_shape = [2] + mvn, reshape_mvn = self.make_mvn( + dims, new_batch_shape, old_batch_shape) + + batch_shape = reshape_mvn.batch_shape_tensor() + event_shape = reshape_mvn.event_shape_tensor() + + expected_sample_shape = [3] + new_batch_shape + [dims] + x = mvn.sample(3, seed=62) + expected_sample = array_ops.reshape(x, expected_sample_shape) + actual_sample = reshape_mvn.sample(3, seed=62) + + expected_log_prob_shape = [3] + new_batch_shape + expected_log_prob = array_ops.reshape( + mvn.log_prob(x), expected_log_prob_shape) + actual_log_prob = reshape_mvn.log_prob(expected_sample) + + with self.test_session() as sess: + [ + batch_shape_, + event_shape_, + expected_sample_, actual_sample_, + expected_log_prob_, actual_log_prob_, + ] = sess.run([ + batch_shape, + event_shape, + expected_sample, actual_sample, + expected_log_prob, actual_log_prob, + ]) + self.assertAllEqual(new_batch_shape, batch_shape_) + self.assertAllEqual([dims], event_shape_) + self.assertAllClose(expected_sample_, actual_sample_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_log_prob_, actual_log_prob_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(new_batch_shape, reshape_mvn.batch_shape) + self.assertAllEqual([dims], reshape_mvn.event_shape) + self.assertAllEqual(expected_sample_shape, actual_sample.shape) + self.assertAllEqual(expected_log_prob_shape, actual_log_prob.shape) + + def test_vector_variate_stats(self): + dims = 3 + new_batch_shape = [2, 1] + old_batch_shape = [2] + mvn, reshape_mvn = self.make_mvn( + dims, new_batch_shape, old_batch_shape) + + expected_scalar_stat_shape = new_batch_shape + + expected_entropy = array_ops.reshape( + mvn.entropy(), expected_scalar_stat_shape) + actual_entropy = reshape_mvn.entropy() + + expected_vector_stat_shape = new_batch_shape + [dims] + + expected_mean = array_ops.reshape( + mvn.mean(), expected_vector_stat_shape) + actual_mean = reshape_mvn.mean() + + expected_mode = array_ops.reshape( + mvn.mode(), expected_vector_stat_shape) + actual_mode = reshape_mvn.mode() + + expected_stddev = array_ops.reshape( + mvn.stddev(), expected_vector_stat_shape) + actual_stddev = reshape_mvn.stddev() + + expected_variance = array_ops.reshape( + mvn.variance(), expected_vector_stat_shape) + actual_variance = reshape_mvn.variance() + + expected_matrix_stat_shape = new_batch_shape + [dims, dims] + + expected_covariance = array_ops.reshape( + mvn.covariance(), expected_matrix_stat_shape) + actual_covariance = reshape_mvn.covariance() + + with self.test_session() as sess: + [ + expected_entropy_, actual_entropy_, + expected_mean_, actual_mean_, + expected_mode_, actual_mode_, + expected_stddev_, actual_stddev_, + expected_variance_, actual_variance_, + expected_covariance_, actual_covariance_, + ] = sess.run([ + expected_entropy, actual_entropy, + expected_mean, actual_mean, + expected_mode, actual_mode, + expected_stddev, actual_stddev, + expected_variance, actual_variance, + expected_covariance, actual_covariance, + ]) + self.assertAllClose(expected_entropy_, actual_entropy_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mean_, actual_mean_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_mode_, actual_mode_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_stddev_, actual_stddev_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_variance_, actual_variance_, + atol=0., rtol=1e-6) + self.assertAllClose(expected_covariance_, actual_covariance_, + atol=0., rtol=1e-6) + if not self.is_static_shape: + return + self.assertAllEqual(expected_scalar_stat_shape, actual_entropy.shape) + self.assertAllEqual(expected_vector_stat_shape, actual_mean.shape) + self.assertAllEqual(expected_vector_stat_shape, actual_mode.shape) + self.assertAllEqual(expected_vector_stat_shape, actual_stddev.shape) + self.assertAllEqual(expected_vector_stat_shape, actual_variance.shape) + self.assertAllEqual(expected_matrix_stat_shape, actual_covariance.shape) + + def test_bad_reshape_size(self): + dims = 2 + new_batch_shape = [2, 3] + old_batch_shape = [2] # 2 != 2*3 + + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = np.ones(old_batch_shape + [dims], self.dtype) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + mvn = mvn_lib.MultivariateNormalDiag(scale_diag=scale_ph) + + if self.is_static_shape: + with self.assertRaisesRegexp( + ValueError, (r"`batch_shape` size \(6\) must match " + r"`distribution\.batch_shape` size \(2\)")): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True) + + else: + with self.test_session(): + with self.assertRaisesOpError(r"`batch_shape` size must match " + r"`distributions.batch_shape` size"): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True).sample().eval() + + def test_non_positive_shape(self): + dims = 2 + new_batch_shape = [-1, -2] # -1*-2=2 so will pass size check. + old_batch_shape = [2] + + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = np.ones(old_batch_shape + [dims], self.dtype) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + mvn = mvn_lib.MultivariateNormalDiag(scale_diag=scale_ph) + + if self.is_static_shape: + with self.assertRaisesRegexp(ValueError, r".*must be positive.*"): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True) + + else: + with self.test_session(): + with self.assertRaisesOpError(r".*must be positive.*"): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True).sample().eval() + + def test_non_vector_shape(self): + dims = 2 + new_batch_shape = 2 + old_batch_shape = [2] + + new_batch_shape_ph = ( + constant_op.constant(np.int32(new_batch_shape)) if self.is_static_shape + else array_ops.placeholder_with_default( + np.int32(new_batch_shape), shape=None)) + + scale = np.ones(old_batch_shape + [dims], self.dtype) + scale_ph = array_ops.placeholder_with_default( + scale, shape=scale.shape if self.is_static_shape else None) + mvn = mvn_lib.MultivariateNormalDiag(scale_diag=scale_ph) + + if self.is_static_shape: + with self.assertRaisesRegexp(ValueError, r".*must be a vector.*"): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True) + + else: + with self.test_session(): + with self.assertRaisesOpError(r".*must be a vector.*"): + batch_reshape_lib.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape_ph, + validate_args=True).sample().eval() + + +class BatchReshapeStaticTest(_BatchReshapeTest, test.TestCase): + + dtype = np.float32 + is_static_shape = True + + +class BatchReshapeDynamicTest(_BatchReshapeTest, test.TestCase): + + dtype = np.float64 + is_static_shape = False + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distributions/python/ops/batch_reshape.py b/tensorflow/contrib/distributions/python/ops/batch_reshape.py new file mode 100644 index 0000000000..c7ee9b2117 --- /dev/null +++ b/tensorflow/contrib/distributions/python/ops/batch_reshape.py @@ -0,0 +1,333 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""The BatchReshape distribution.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import tensor_shape +from tensorflow.python.framework import tensor_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import check_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops.distributions import distribution as distribution_lib + + +__all__ = [ + "BatchReshape", +] + + +class BatchReshape(distribution_lib.Distribution): + """The Batch-Reshaping distribution. + + This "meta-distribution" reshapes the batch dimensions of another + distribution. + + Note: Unlike `tf.reshape`, the `BatchReshape` distribution does not support + `-1` for flattening. + + #### Examples + + ```python + tfd = tf.contrib.distributions + + dtype = np.float32 + dims = 2 + new_batch_shape = [1, 2, 3] + old_batch_shape = [6] + + scale = np.ones(old_batch_shape + [dims], dtype) + mvn = tfd.MultivariateNormalDiag(scale_diag=scale) + reshape_mvn = tfd.BatchReshape( + distribution=mvn, + batch_shape=new_batch_shape, + validate_args=True) + + reshape_mvn.batch_shape + # ==> [1, 2, 3] + + x = reshape_mvn.sample(sample_shape=[4, 5]) + x.shape + # ==> [4, 5, 1, 2, 3, 2] == sample_shape + new_batch_shape + [dims] + + reshape_mvn.log_prob(x).shape + # ==> [4, 5, 1, 2, 3] == sample_shape + new_batch_shape + ``` + + """ + + def __init__(self, + distribution, + batch_shape, + validate_args=False, + allow_nan_stats=True, + name=None): + """Construct BatchReshape distribution. + + Args: + distribution: The base distribution instance to reshape. Typically an + instance of `Distribution`. + batch_shape: Positive `int`-like vector-shaped `Tensor` representing the + new shape of the batch dimensions. + validate_args: Python `bool`, default `False`. When `True` distribution + parameters are checked for validity despite possibly degrading runtime + performance. When `False` invalid inputs may silently render incorrect + outputs. + allow_nan_stats: Python `bool`, default `True`. When `True`, statistics + (e.g., mean, mode, variance) use the value "`NaN`" to indicate the + result is undefined. When `False`, an exception is raised if one or + more of the statistic's batch members are undefined. + name: The name to give Ops created by the initializer. + Default value: `"BatchReshape" + distribution.name`. + + Raises: + ValueError: if `batch_shape` is not a vector. + ValueError: if `batch_shape` has non-positive elements. + ValueError: if `batch_shape` size is not the same as a + `distribution.batch_shape` size. + """ + parameters = locals() + name = name or "BatchReshape" + distribution.name + self._distribution = distribution + with ops.name_scope(name, values=[batch_shape]) as name: + self._batch_shape_ = ops.convert_to_tensor( + batch_shape, + dtype=dtypes.int32, + name="batch_shape") + self._batch_shape_static = tensor_util.constant_value(self._batch_shape_) + if self._batch_shape_static is not None: + self._batch_shape_static = np.int32(self._batch_shape_static) + self._runtime_assertions = make_runtime_assertions( + self._distribution, + self._batch_shape_, + validate_args, + self._batch_shape_static) + super(BatchReshape, self).__init__( + dtype=self._distribution.dtype, + reparameterization_type=self._distribution.reparameterization_type, + validate_args=validate_args, + allow_nan_stats=allow_nan_stats, + parameters=parameters, + graph_parents=( + [self._batch_shape_] + + self._distribution._graph_parents), # pylint: disable=protected-access + name=name) + + @property + def distribution(self): + return self._distribution + + def _batch_shape_tensor(self): + with ops.control_dependencies(self._runtime_assertions): + return array_ops.identity(self._batch_shape_) + + def _batch_shape(self): + return tensor_shape.TensorShape(self._batch_shape_static) + + def _event_shape_tensor(self): + with ops.control_dependencies(self._runtime_assertions): + return array_ops.identity(self.distribution.event_shape_tensor()) + + def _event_shape(self): + return self.distribution.event_shape + + def _sample_n(self, n, seed=None): + with ops.control_dependencies(self._runtime_assertions): + x = self.distribution.sample(sample_shape=n, seed=seed) + new_shape = array_ops.concat([ + [n], + self.batch_shape_tensor(), + self.event_shape_tensor(), + ], axis=0) + return array_ops.reshape(x, new_shape) + + def _log_prob(self, x): + return self._call_reshape_input_output( + self.distribution.log_prob, x) + + def _prob(self, x): + return self._call_reshape_input_output( + self.distribution.prob, x) + + def _log_cdf(self, x): + return self._call_reshape_input_output( + self.distribution.log_cdf, x) + + def _cdf(self, x): + return self._call_reshape_input_output( + self.distribution.cdf, x) + + def _log_survival_function(self, x): + return self._call_reshape_input_output( + self.distribution.log_survival_function, x) + + def _survival_function(self, x): + return self._call_reshape_input_output( + self.distribution.survival_function, x) + + def _entropy(self): + return self._call_and_reshape_output( + self.distribution.entropy, + [], + [tensor_shape.scalar()]) + + def _mean(self): + return self._call_and_reshape_output(self.distribution.mean) + + def _mode(self): + return self._call_and_reshape_output(self.distribution.mode) + + def _stddev(self): + return self._call_and_reshape_output(self.distribution.stddev) + + def _variance(self): + return self._call_and_reshape_output(self.distribution.variance) + + def _covariance(self): + return self._call_and_reshape_output( + self.distribution.covariance, + [self.event_shape_tensor()]*2, + [self.event_shape]*2) + + def _sample_shape(self, x): + """Computes graph and static `sample_shape`.""" + x_ndims = (array_ops.rank(x) if x.shape.ndims is None else x.shape.ndims) + event_ndims = (array_ops.size(self.event_shape_tensor()) + if self.event_shape.ndims is None + else self.event_shape.ndims) + batch_ndims = (array_ops.size(self.batch_shape_tensor()) + if self.batch_shape.ndims is None + else self.batch_shape.ndims) + sample_ndims = x_ndims - batch_ndims - event_ndims + if isinstance(sample_ndims, int): + static_sample_shape = x.shape[:sample_ndims] + else: + static_sample_shape = tensor_shape.TensorShape(None) + if static_sample_shape.is_fully_defined(): + sample_shape = np.int32(static_sample_shape.as_list()) + else: + sample_shape = array_ops.shape(x)[:sample_ndims] + return sample_shape, static_sample_shape + + def _call_reshape_input_output(self, fn, x): + """Calls `fn`, appropriately reshaping its input `x` and output.""" + with ops.control_dependencies(self._runtime_assertions): + sample_shape, static_sample_shape = self._sample_shape(x) + old_shape = array_ops.concat([ + sample_shape, + self.distribution.batch_shape_tensor(), + self.event_shape_tensor(), + ], axis=0) + result = fn(array_ops.reshape(x, old_shape)) + new_shape = array_ops.concat([ + sample_shape, + self.batch_shape_tensor(), + ], axis=0) + result = array_ops.reshape(result, new_shape) + if (static_sample_shape.ndims is not None and + self.batch_shape.ndims is not None): + new_shape = static_sample_shape.concatenate(self.batch_shape) + result.set_shape(result.shape.merge_with(new_shape)) + return result + + def _call_and_reshape_output( + self, + fn, + event_shape_list=None, + static_event_shape_list=None): + """Calls `fn` and appropriately reshapes its output.""" + with ops.control_dependencies(self._runtime_assertions): + if event_shape_list is None: + event_shape_list = [self._event_shape_tensor()] + if static_event_shape_list is None: + static_event_shape_list = [self.event_shape] + new_shape = array_ops.concat( + [self.batch_shape_tensor()] + event_shape_list, + axis=0) + result = array_ops.reshape(fn(), new_shape) + if (self.batch_shape.ndims is not None and + self.event_shape.ndims is not None): + event_shape = tensor_shape.TensorShape([]) + for rss in static_event_shape_list: + event_shape = event_shape.concatenate(rss) + static_shape = result.shape.merge_with( + self.batch_shape.concatenate(event_shape)) + result.set_shape(static_shape) + return result + + +def make_runtime_assertions( + distribution, + batch_shape, + validate_args, + batch_shape_static): + """Helper to __init__ which makes or raises assertions.""" + runtime_assertions = [] + + if batch_shape.shape.ndims is not None: + if batch_shape.shape.ndims != 1: + raise ValueError("`batch_shape` must be a vector " + "(saw rank: {}).".format( + batch_shape.shape.ndims)) + elif validate_args: + runtime_assertions += [ + check_ops.assert_rank( + batch_shape, + 1, + message="`batch_shape` must be a vector.", + name="assert_batch_shape_is_vector"), + ] + + batch_size_static = np.prod(batch_shape_static) + dist_batch_size_static = ( + None if not distribution.batch_shape.is_fully_defined() + else np.prod(distribution.batch_shape).value) + + if batch_size_static is not None and dist_batch_size_static is not None: + if batch_size_static != dist_batch_size_static: + raise ValueError("`batch_shape` size ({}) must match " + "`distribution.batch_shape` size ({}).".format( + batch_size_static, + dist_batch_size_static)) + elif validate_args: + runtime_assertions += [ + check_ops.assert_equal( + math_ops.reduce_prod(batch_shape), + math_ops.reduce_prod(distribution.batch_shape_tensor()), + message=("`batch_shape` size must match " + "`distributions.batch_shape` size."), + name="assert_batch_size"), + ] + + if batch_shape_static is not None: + if np.any(batch_shape_static < 1): + raise ValueError("`batch_shape` elements must be positive " + "(i.e., larger than zero).") + elif validate_args: + runtime_assertions += [ + check_ops.assert_positive( + batch_shape, + message=("`batch_shape` elements must be positive " + "(i.e., larger than zero)."), + name="assert_batch_shape_positive") + ] + + return runtime_assertions -- GitLab From 86868a156860877fc6e8c3393baf4942b6b7dbd4 Mon Sep 17 00:00:00 2001 From: Andrew Selle Date: Thu, 29 Mar 2018 07:59:46 -0700 Subject: [PATCH 405/906] Disable the toco binary in pip feature until it can used shared libs (#18061) * Disable the toco binary in pip feature until it can used shared libraries. The binary size was doubled by the saved model change. Since to process saved models most of the TensorFlow runtime is needed. A workaround to this is in the works and should be submitted in the next couple weeks. * Fix linter errors with unused tensorflow libs * Mollify the linter by removing os. --- tensorflow/contrib/lite/toco/python/BUILD | 3 --- tensorflow/contrib/lite/toco/python/toco_wrapper.py | 13 +++++++++---- tensorflow/tools/pip_package/build_pip_package.sh | 4 +++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tensorflow/contrib/lite/toco/python/BUILD b/tensorflow/contrib/lite/toco/python/BUILD index 17115047d2..86d91bd3be 100644 --- a/tensorflow/contrib/lite/toco/python/BUILD +++ b/tensorflow/contrib/lite/toco/python/BUILD @@ -45,9 +45,6 @@ py_binary( name = "toco_wrapper", srcs = ["toco_wrapper.py"], srcs_version = "PY2AND3", - deps = [ - "//tensorflow:tensorflow_py", - ], ) tf_py_test( diff --git a/tensorflow/contrib/lite/toco/python/toco_wrapper.py b/tensorflow/contrib/lite/toco/python/toco_wrapper.py index e39b5f22c7..6d6b500d7e 100644 --- a/tensorflow/contrib/lite/toco/python/toco_wrapper.py +++ b/tensorflow/contrib/lite/toco/python/toco_wrapper.py @@ -22,14 +22,19 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import os import sys -import tensorflow as tf def main(): # Pip installs the binary in aux-bin off of main site-package install. # Just find it and exec, passing all arguments in the process. # TODO(aselle): it is unfortunate to use all of tensorflow to lookup binary. - binary = os.path.join(tf.__path__[0], 'aux-bin/toco') - os.execvp(binary, sys.argv) + print("""TOCO from pip install is currently not working on command line. +Please use the python TOCO API or use +bazel run tensorflow/contrib/lite:toco -- from a TensorFlow source dir. +""") + sys.exit(1) + # TODO(aselle): Replace this when we find a way to run toco without + # blowing up executable size. + # binary = os.path.join(tf.__path__[0], 'aux-bin/toco') + # os.execvp(binary, sys.argv) diff --git a/tensorflow/tools/pip_package/build_pip_package.sh b/tensorflow/tools/pip_package/build_pip_package.sh index dc31e4c5f7..feb3114bde 100755 --- a/tensorflow/tools/pip_package/build_pip_package.sh +++ b/tensorflow/tools/pip_package/build_pip_package.sh @@ -139,7 +139,9 @@ function main() { fi mkdir "${TMPDIR}/tensorflow/aux-bin" # Install toco as a binary in aux-bin. - cp bazel-bin/tensorflow/contrib/lite/toco/toco ${TMPDIR}/tensorflow/aux-bin/ + # TODO(aselle): Re-enable this when we find a way to do it without doubling + # the whl size (over the limit). + # cp bazel-bin/tensorflow/contrib/lite/toco/toco ${TMPDIR}/tensorflow/aux-bin/ fi # protobuf pip package doesn't ship with header files. Copy the headers -- GitLab From 608fb59318ca0a1f2a05fae4d23b06cf6e162300 Mon Sep 17 00:00:00 2001 From: Rachel Lim Date: Thu, 29 Mar 2018 08:19:17 -0700 Subject: [PATCH 406/906] [tf.data] Optimizations on make_csv_dataset internals. PiperOrigin-RevId: 190933143 --- tensorflow/contrib/data/python/ops/readers.py | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/tensorflow/contrib/data/python/ops/readers.py b/tensorflow/contrib/data/python/ops/readers.py index 95edca6cdd..9a48aa02fb 100644 --- a/tensorflow/contrib/data/python/ops/readers.py +++ b/tensorflow/contrib/data/python/ops/readers.py @@ -18,9 +18,11 @@ from __future__ import division from __future__ import print_function import csv +from math import ceil import numpy as np +from tensorflow.contrib.data.python.ops import batching from tensorflow.contrib.data.python.ops import interleave_ops from tensorflow.contrib.data.python.ops import shuffle_ops from tensorflow.python.data.ops import dataset_ops @@ -176,6 +178,9 @@ def make_csv_dataset( shuffle_buffer_size=10000, shuffle_seed=None, prefetch_buffer_size=1, + num_parallel_reads=1, + num_parallel_parser_calls=2, + sloppy=False, default_float_type=dtypes.float32, num_rows_for_inference=100, ): @@ -231,6 +236,15 @@ def make_csv_dataset( prefetch_buffer_size: An int specifying the number of feature batches to prefetch for performance improvement. Recommended value is the number of batches consumed per training step. + num_parallel_reads: Number of threads used to read CSV records from files. + If >1, the results will be interleaved. + num_parallel_parser_calls: Number of parallel invocations of the CSV parsing + function on CSV records. + sloppy: If `True`, reading performance will be improved at + the cost of non-deterministic ordering. If `False`, the order of elements + produced is deterministic prior to shuffling (elements are still + randomized if `shuffle=True`. Note that if the seed is set, then order + of elements after shuffling is deterministic). Defaults to `False`. default_float_type: Either `tf.float32` or `tf.float64`. If defaults are not provided, float-like strings are interpreted to be this type. num_rows_for_inference: Number of rows of a file to use for type inference @@ -247,11 +261,16 @@ def make_csv_dataset( Raises: ValueError: If any of the arguments is malformed. """ - filenames = _get_file_names(file_pattern, shuffle) + # Create dataset of all matching filenames + filenames = _get_file_names(file_pattern, False) + dataset = dataset_ops.Dataset.from_tensor_slices(filenames) + if shuffle: + dataset = dataset.shuffle(len(filenames), shuffle_seed) + + # Clean arguments; figure out column names and defaults if comment is not None and len(comment) != 1: raise ValueError("`comment` arg must be a single-character string or None") - # Clean arguments; figure out column names and defaults if column_names is None: if not header: raise ValueError("Cannot infer column names without a header line.") @@ -272,7 +291,6 @@ def make_csv_dataset( filenames, len(column_names), field_delim, use_quote_delim, na_value, header, comment, default_float_type, num_rows_for_inference) - dataset = dataset_ops.Dataset.from_tensor_slices(filenames) if label_name is not None and label_name not in column_names: raise ValueError("`label_name` provided must be one of the columns.") @@ -311,16 +329,31 @@ def make_csv_dataset( return features, label return features - # TODO(rachelim): interleave records from files for better shuffling - dataset = dataset.flat_map(filename_to_dataset) - # TODO(rachelim): use fused shuffle_and_repeat for perf - if shuffle: + # Read files sequentially or in parallel + dataset = dataset.apply( + interleave_ops.parallel_interleave( + filename_to_dataset, cycle_length=num_parallel_reads, sloppy=sloppy)) + + if num_epochs != 1 and shuffle: + # Use shuffle_and_repeat for perf + dataset = dataset.apply( + shuffle_ops.shuffle_and_repeat(shuffle_buffer_size, num_epochs, + shuffle_seed)) + elif shuffle: dataset = dataset.shuffle(shuffle_buffer_size, shuffle_seed) - if num_epochs != 1: + elif num_epochs != 1: dataset = dataset.repeat(num_epochs) - dataset = dataset.batch(batch_size) - dataset = dataset.map(decode_csv) + # Use map_and_batch for perf + # TODO(b/76425672): use num_parallel_calls for better performance tuning when + # that is added + dataset = dataset.apply( + batching.map_and_batch( + map_func=decode_csv, + batch_size=batch_size, + num_parallel_batches=int( + ceil(num_parallel_parser_calls / batch_size)))) + dataset = dataset.prefetch(prefetch_buffer_size) return dataset @@ -416,12 +449,10 @@ def make_batched_features_dataset(file_pattern, `Tensor` or `SparseTensor` objects. """ # Create dataset of all matching filenames + filenames = _get_file_names(file_pattern, False) + dataset = dataset_ops.Dataset.from_tensor_slices(filenames) if shuffle: - dataset = dataset_ops.Dataset.list_files(file_pattern, shuffle=True) - else: - # TODO(b/73959787): Use Dataset.list_files() once ordering is deterministic. - filenames = _get_file_names(file_pattern, shuffle) - dataset = dataset_ops.Dataset.from_tensor_slices(filenames) + dataset = dataset.shuffle(len(filenames), shuffle_seed) # Read `Example` records from files as tensor objects. if reader_args is None: -- GitLab From a98351b9c6c691b6873ef5a5c3e8e48bf42bd14c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 09:41:53 -0700 Subject: [PATCH 407/906] Upgrade Eigen version. PiperOrigin-RevId: 190942370 --- tensorflow/core/kernels/cwise_ops.h | 21 --------------------- tensorflow/workspace.bzl | 8 ++++---- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/tensorflow/core/kernels/cwise_ops.h b/tensorflow/core/kernels/cwise_ops.h index 06918075a4..a80905d145 100644 --- a/tensorflow/core/kernels/cwise_ops.h +++ b/tensorflow/core/kernels/cwise_ops.h @@ -27,27 +27,6 @@ limitations under the License. #include "tensorflow/core/kernels/bounds_check.h" namespace Eigen { -namespace numext { -#if GOOGLE_CUDA -template <> -EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE std::complex exp( - const std::complex& x) { - auto com = ::expf(x.real()); - auto res_real = com * ::cosf(x.imag()); - auto res_imag = com * ::sinf(x.imag()); - return std::complex(res_real, res_imag); -} -template <> -EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE std::complex exp( - const std::complex& x) { - auto com = ::exp(x.real()); - auto res_real = com * ::cos(x.imag()); - auto res_imag = com * ::sin(x.imag()); - return std::complex(res_real, res_imag); -} -#endif -} // namespace numext - namespace internal { template diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 9fcbfb664b..0e31358236 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -74,11 +74,11 @@ def tf_workspace(path_prefix="", tf_repo_name=""): tf_http_archive( name = "eigen_archive", urls = [ - "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/2355b229ea4c.tar.gz", - "https://bitbucket.org/eigen/eigen/get/2355b229ea4c.tar.gz", + "https://mirror.bazel.build/bitbucket.org/eigen/eigen/get/6913f0cf7d06.tar.gz", + "https://bitbucket.org/eigen/eigen/get/6913f0cf7d06.tar.gz", ], - sha256 = "0cadb31a35b514bf2dfd6b5d38205da94ef326ec6908fc3fd7c269948467214f", - strip_prefix = "eigen-eigen-2355b229ea4c", + sha256 = "791b836cacd03e20bae5bdd25f1c4a5505a0a9975ba94a61eb4e2631fbd1d53a", + strip_prefix = "eigen-eigen-6913f0cf7d06", build_file = str(Label("//third_party:eigen.BUILD")), patch_file = str(Label("//third_party:eigen_fix_cuda_compilation.patch")) ) -- GitLab From ae3d20f9aef78554f0d0f5eec13982e9802a45d2 Mon Sep 17 00:00:00 2001 From: Jacques Pienaar Date: Thu, 29 Mar 2018 09:42:05 -0700 Subject: [PATCH 408/906] Add bitcast for equal bitwidth casts. Map bitcasts to XLA bitcast HLO if the bitwidth of the elementtype is the same. PiperOrigin-RevId: 190942398 --- tensorflow/compiler/tests/unary_ops_test.py | 14 ++++++ tensorflow/compiler/tf2xla/kernels/cast_op.cc | 45 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/tensorflow/compiler/tests/unary_ops_test.py b/tensorflow/compiler/tests/unary_ops_test.py index 3d3e112f48..a8ab235378 100644 --- a/tensorflow/compiler/tests/unary_ops_test.py +++ b/tensorflow/compiler/tests/unary_ops_test.py @@ -600,6 +600,20 @@ class UnaryOpsTest(XLATestCase): src, expected=dst) + def testBitcast(self): + self._assertOpOutputMatchesExpected( + lambda x: array_ops.bitcast(x, dtypes.int32), + np.array([1, 0x3f800000], np.int32), + expected=np.array([1, 0x3f800000], np.int32)) + self._assertOpOutputMatchesExpected( + lambda x: array_ops.bitcast(x, dtypes.float32), + np.array([1, 0x3f800000], np.int32), + expected=np.array([1e-45, 1.0], np.float32)) + self._assertOpOutputMatchesExpected( + lambda x: array_ops.bitcast(x, dtypes.int32), + np.array([1e-45, 1.0], np.float32), + expected=np.array([1, 0x3f800000], np.int32)) + def testInvertPermutation(self): self._assertOpOutputMatchesExpected( array_ops.invert_permutation, diff --git a/tensorflow/compiler/tf2xla/kernels/cast_op.cc b/tensorflow/compiler/tf2xla/kernels/cast_op.cc index 43a6a747c6..c52b2dcb7e 100644 --- a/tensorflow/compiler/tf2xla/kernels/cast_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/cast_op.cc @@ -62,5 +62,50 @@ class CastOp : public XlaOpKernel { REGISTER_XLA_OP(Name("Cast"), CastOp); +class BitcastOp : public XlaOpKernel { + public: + explicit BitcastOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) { + OP_REQUIRES_OK(ctx, ctx->GetAttr("T", &src_dtype_)); + OP_REQUIRES_OK(ctx, ctx->GetAttr("type", &dst_dtype_)); + OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(src_dtype_, &src_type_)); + OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(dst_dtype_, &dst_type_)); + } + + void Compile(XlaOpKernelContext* ctx) override { + xla::ComputationBuilder* builder = ctx->builder(); + xla::ComputationDataHandle input = ctx->Input(0); + xla::ComputationDataHandle output; + + if (src_dtype_ == dst_dtype_) { + output = input; + } else { + // The only complex type in XLA is C64, so error out if the bitcast has a + // complex source or destination type and the bitcast is not trivial. + OP_REQUIRES(ctx, + !xla::primitive_util::IsComplexType(src_type_) && + !xla::primitive_util::IsComplexType(dst_type_), + errors::Unimplemented("Complex types not supported.")); + // XLA bitcast requires that the bit-width of the source and destination + // matches, and currently only the simple lowering is performed. + OP_REQUIRES(ctx, + xla::primitive_util::BitWidth(src_type_) == + xla::primitive_util::BitWidth(dst_type_), + errors::Unimplemented( + "Only bitcasts between equally sized types supported.")); + output = builder->BitcastConvertType(input, dst_type_); + } + + ctx->SetOutput(0, output); + } + + protected: + DataType src_dtype_, dst_dtype_; + xla::PrimitiveType src_type_, dst_type_; + + TF_DISALLOW_COPY_AND_ASSIGN(BitcastOp); +}; + +REGISTER_XLA_OP(Name("Bitcast"), BitcastOp); + } // anonymous namespace } // namespace tensorflow -- GitLab From a2b6c3c124664d682094a1ecfa9cc00cca8ada85 Mon Sep 17 00:00:00 2001 From: Younghee Kwon Date: Thu, 29 Mar 2018 09:43:19 -0700 Subject: [PATCH 409/906] Added kernels and estimators for Gradient Boosting Trees algorithm. BoostedTreesClassifier and BoostedTreesRegressor are added to tf.estimator. Also some training utility functions are added to tf.contrib.estimator. PiperOrigin-RevId: 190942599 --- tensorflow/contrib/cmake/python_modules.txt | 1 + tensorflow/contrib/cmake/python_protos.txt | 1 + tensorflow/contrib/cmake/tf_core_ops.cmake | 7 +- tensorflow/contrib/cmake/tf_python.cmake | 1 + tensorflow/contrib/estimator/BUILD | 31 + tensorflow/contrib/estimator/__init__.py | 3 + .../python/estimator/boosted_trees.py | 323 ++++ .../python/estimator/boosted_trees_test.py | 207 +++ tensorflow/contrib/makefile/tf_op_files.txt | 6 + .../contrib/makefile/tf_proto_files.txt | 1 + tensorflow/core/BUILD | 3 + ...tedTreesCalculateBestGainsPerFeature.pbtxt | 87 + .../api_def_BoostedTreesCreateEnsemble.pbtxt | 23 + ..._def_BoostedTreesDeserializeEnsemble.pbtxt | 26 + ...BoostedTreesEnsembleResourceHandleOp.pbtxt | 5 + ...pi_def_BoostedTreesGetEnsembleStates.pbtxt | 35 + ...api_def_BoostedTreesMakeStatsSummary.pbtxt | 56 + .../api_def_BoostedTreesPredict.pbtxt | 41 + ...pi_def_BoostedTreesSerializeEnsemble.pbtxt | 23 + .../api_def_BoostedTreesTrainingPredict.pbtxt | 69 + .../api_def_BoostedTreesUpdateEnsemble.pbtxt | 82 + ...ef_IsBoostedTreesEnsembleInitialized.pbtxt | 17 + tensorflow/core/kernels/BUILD | 7 + tensorflow/core/kernels/boosted_trees/BUILD | 89 + .../kernels/boosted_trees/boosted_trees.proto | 113 ++ .../kernels/boosted_trees/prediction_ops.cc | 263 +++ .../kernels/boosted_trees/resource_ops.cc | 189 +++ .../core/kernels/boosted_trees/resources.cc | 301 ++++ .../core/kernels/boosted_trees/resources.h | 221 +++ .../core/kernels/boosted_trees/stats_ops.cc | 296 ++++ .../kernels/boosted_trees/training_ops.cc | 219 +++ tensorflow/core/ops/boosted_trees_ops.cc | 319 ++++ tensorflow/python/BUILD | 22 + tensorflow/python/__init__.py | 2 + tensorflow/python/estimator/BUILD | 48 + .../python/estimator/canned/boosted_trees.py | 736 +++++++++ .../estimator/canned/boosted_trees_test.py | 799 +++++++++ tensorflow/python/estimator/estimator_lib.py | 4 + .../python/kernel_tests/boosted_trees/BUILD | 76 + .../kernel_tests/boosted_trees/__init__.py | 0 .../boosted_trees/prediction_ops_test.py | 926 +++++++++++ .../boosted_trees/resource_ops_test.py | 228 +++ .../boosted_trees/stats_ops_test.py | 289 ++++ .../boosted_trees/training_ops_test.py | 1465 +++++++++++++++++ tensorflow/python/ops/boosted_trees_ops.py | 160 ++ tensorflow/python/training/device_setter.py | 13 +- ....estimator.-boosted-trees-classifier.pbtxt | 54 + ...w.estimator.-boosted-trees-regressor.pbtxt | 54 + .../api/golden/tensorflow.estimator.pbtxt | 8 + 49 files changed, 7939 insertions(+), 10 deletions(-) create mode 100644 tensorflow/contrib/estimator/python/estimator/boosted_trees.py create mode 100644 tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesCalculateBestGainsPerFeature.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesCreateEnsemble.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesDeserializeEnsemble.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesEnsembleResourceHandleOp.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesGetEnsembleStates.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesMakeStatsSummary.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesPredict.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesSerializeEnsemble.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesTrainingPredict.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_BoostedTreesUpdateEnsemble.pbtxt create mode 100644 tensorflow/core/api_def/base_api/api_def_IsBoostedTreesEnsembleInitialized.pbtxt create mode 100644 tensorflow/core/kernels/boosted_trees/BUILD create mode 100644 tensorflow/core/kernels/boosted_trees/boosted_trees.proto create mode 100644 tensorflow/core/kernels/boosted_trees/prediction_ops.cc create mode 100644 tensorflow/core/kernels/boosted_trees/resource_ops.cc create mode 100644 tensorflow/core/kernels/boosted_trees/resources.cc create mode 100644 tensorflow/core/kernels/boosted_trees/resources.h create mode 100644 tensorflow/core/kernels/boosted_trees/stats_ops.cc create mode 100644 tensorflow/core/kernels/boosted_trees/training_ops.cc create mode 100644 tensorflow/core/ops/boosted_trees_ops.cc create mode 100644 tensorflow/python/estimator/canned/boosted_trees.py create mode 100644 tensorflow/python/estimator/canned/boosted_trees_test.py create mode 100644 tensorflow/python/kernel_tests/boosted_trees/BUILD create mode 100644 tensorflow/python/kernel_tests/boosted_trees/__init__.py create mode 100644 tensorflow/python/kernel_tests/boosted_trees/prediction_ops_test.py create mode 100644 tensorflow/python/kernel_tests/boosted_trees/resource_ops_test.py create mode 100644 tensorflow/python/kernel_tests/boosted_trees/stats_ops_test.py create mode 100644 tensorflow/python/kernel_tests/boosted_trees/training_ops_test.py create mode 100644 tensorflow/python/ops/boosted_trees_ops.py create mode 100644 tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt create mode 100644 tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index 112b690511..cc7d791042 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -79,6 +79,7 @@ tensorflow/python/keras/_impl/keras/preprocessing tensorflow/python/keras/_impl/keras/utils tensorflow/python/keras/_impl/keras/wrappers tensorflow/python/kernel_tests +tensorflow/python/kernel_tests/boosted_trees tensorflow/python/kernel_tests/distributions tensorflow/python/kernel_tests/linalg tensorflow/python/kernel_tests/random diff --git a/tensorflow/contrib/cmake/python_protos.txt b/tensorflow/contrib/cmake/python_protos.txt index c03c0c80fe..0c80d529af 100644 --- a/tensorflow/contrib/cmake/python_protos.txt +++ b/tensorflow/contrib/cmake/python_protos.txt @@ -1,4 +1,5 @@ tensorflow/core +tensorflow/core/kernels/boosted_trees tensorflow/core/profiler tensorflow/python tensorflow/contrib/boosted_trees/proto diff --git a/tensorflow/contrib/cmake/tf_core_ops.cmake b/tensorflow/contrib/cmake/tf_core_ops.cmake index d6712aa2b4..092a48bc6b 100644 --- a/tensorflow/contrib/cmake/tf_core_ops.cmake +++ b/tensorflow/contrib/cmake/tf_core_ops.cmake @@ -15,8 +15,9 @@ set(tf_op_lib_names "audio_ops" "array_ops" - "batch_ops" + "batch_ops" "bitwise_ops" + "boosted_trees_ops" "candidate_sampling_ops" "checkpoint_ops" "control_flow_ops" @@ -28,7 +29,7 @@ set(tf_op_lib_names "image_ops" "io_ops" "linalg_ops" - "list_ops" + "list_ops" "lookup_ops" "logging_ops" "manip_ops" @@ -48,7 +49,7 @@ set(tf_op_lib_names "state_ops" "stateless_random_ops" "string_ops" - "summary_ops" + "summary_ops" "training_ops" ) diff --git a/tensorflow/contrib/cmake/tf_python.cmake b/tensorflow/contrib/cmake/tf_python.cmake index 31e715b654..b776307924 100755 --- a/tensorflow/contrib/cmake/tf_python.cmake +++ b/tensorflow/contrib/cmake/tf_python.cmake @@ -319,6 +319,7 @@ GENERATE_PYTHON_OP_LIB("audio_ops") GENERATE_PYTHON_OP_LIB("array_ops") GENERATE_PYTHON_OP_LIB("batch_ops") GENERATE_PYTHON_OP_LIB("bitwise_ops") +GENERATE_PYTHON_OP_LIB("boosted_trees_ops") GENERATE_PYTHON_OP_LIB("math_ops") GENERATE_PYTHON_OP_LIB("functional_ops") GENERATE_PYTHON_OP_LIB("candidate_sampling_ops") diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index d125e40f6c..2be62c9438 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -14,6 +14,7 @@ py_library( srcs = ["__init__.py"], srcs_version = "PY2AND3", deps = [ + ":boosted_trees", ":dnn", ":dnn_linear_combined", ":extenders", @@ -26,6 +27,36 @@ py_library( ], ) +py_library( + name = "boosted_trees", + srcs = ["python/estimator/boosted_trees.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python/estimator", + "//tensorflow/python/estimator:boosted_trees", + ], +) + +py_test( + name = "boosted_trees_test", + size = "medium", + srcs = ["python/estimator/boosted_trees_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + "notsan", + ], + deps = [ + ":boosted_trees", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:training", + "//tensorflow/python/estimator:numpy_io", + "//tensorflow/python/feature_column", + "//third_party/py/numpy", + ], +) + py_library( name = "dnn", srcs = ["python/estimator/dnn.py"], diff --git a/tensorflow/contrib/estimator/__init__.py b/tensorflow/contrib/estimator/__init__.py index 6b9f9575b6..d2fc2c4bfa 100644 --- a/tensorflow/contrib/estimator/__init__.py +++ b/tensorflow/contrib/estimator/__init__.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function # pylint: disable=unused-import,line-too-long,wildcard-import +from tensorflow.contrib.estimator.python.estimator.boosted_trees import * from tensorflow.contrib.estimator.python.estimator.dnn import * from tensorflow.contrib.estimator.python.estimator.dnn_linear_combined import * from tensorflow.contrib.estimator.python.estimator.extenders import * @@ -44,6 +45,8 @@ _allowed_symbols = [ 'DNNEstimator', 'DNNLinearCombinedEstimator', 'LinearEstimator', + 'boosted_trees_classifier_train_in_memory', + 'boosted_trees_regressor_train_in_memory', 'call_logit_fn', 'dnn_logit_fn_builder', 'linear_logit_fn_builder', diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py new file mode 100644 index 0000000000..5880164519 --- /dev/null +++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py @@ -0,0 +1,323 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Boosted Trees estimators.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.estimator import estimator +from tensorflow.python.estimator.canned import boosted_trees as canned_boosted_trees + + +class _BoostedTreesEstimator(estimator.Estimator): + """An Estimator for Tensorflow Boosted Trees models.""" + + def __init__(self, + feature_columns, + n_batches_per_layer, + head, + model_dir=None, + weight_column=None, + n_trees=100, + max_depth=6, + learning_rate=0.1, + l1_regularization=0., + l2_regularization=0., + tree_complexity=0., + config=None): + """Initializes a `BoostedTreesEstimator` instance. + + Args: + feature_columns: An iterable containing all the feature columns used by + the model. All items in the set should be instances of classes derived + from `FeatureColumn`. + n_batches_per_layer: the number of batches to collect statistics per + layer. + head: the `Head` instance defined for Estimator. + model_dir: Directory to save model parameters, graph and etc. This can + also be used to load checkpoints from the directory into a estimator + to continue training a previously saved model. + weight_column: A string or a `_NumericColumn` created by + `tf.feature_column.numeric_column` defining feature column representing + weights. It is used to downweight or boost examples during training. It + will be multiplied by the loss of the example. If it is a string, it is + used as a key to fetch weight tensor from the `features`. If it is a + `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, + then weight_column.normalizer_fn is applied on it to get weight tensor. + n_trees: number trees to be created. + max_depth: maximum depth of the tree to grow. + learning_rate: shrinkage parameter to be used when a tree added to the + model. + l1_regularization: regularization multiplier applied to the absolute + weights of the tree leafs. + l2_regularization: regularization multiplier applied to the square weights + of the tree leafs. + tree_complexity: regularization factor to penalize trees with more leaves. + config: `RunConfig` object to configure the runtime settings. + """ + # TODO(youngheek): param validations. + + # HParams for the model. + tree_hparams = canned_boosted_trees.TreeHParams( + n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, + tree_complexity) + + def _model_fn(features, labels, mode, config): + return canned_boosted_trees._bt_model_fn( # pylint: disable=protected-access + features, labels, mode, head, feature_columns, tree_hparams, + n_batches_per_layer, config) + + super(_BoostedTreesEstimator, self).__init__( + model_fn=_model_fn, model_dir=model_dir, config=config) + + +def boosted_trees_classifier_train_in_memory( + train_input_fn, + feature_columns, + model_dir=None, + n_classes=canned_boosted_trees._HOLD_FOR_MULTI_CLASS_SUPPORT, + weight_column=None, + label_vocabulary=None, + n_trees=100, + max_depth=6, + learning_rate=0.1, + l1_regularization=0., + l2_regularization=0., + tree_complexity=0., + config=None, + train_hooks=None): + """Trains a boosted tree classifier with in memory dataset. + + Example: + + ```python + bucketized_feature_1 = bucketized_column( + numeric_column('feature_1'), BUCKET_BOUNDARIES_1) + bucketized_feature_2 = bucketized_column( + numeric_column('feature_2'), BUCKET_BOUNDARIES_2) + + def input_fn_train(): + dataset = create-dataset-from-training-data + # Don't use repeat or cache, since it is assumed to be one epoch + # This is either tf.data.Dataset, or a tuple of feature dict and label. + return dataset + + classifier = boosted_trees_classifier_train_in_memory( + train_input_fn, + feature_columns=[bucketized_feature_1, bucketized_feature_2], + n_trees=100, + ... + ) + + def input_fn_eval(): + ... + return dataset + + metrics = classifier.evaluate(input_fn=input_fn_eval, steps=10) + ``` + + Args: + train_input_fn: the input function returns a dataset containing a single + epoch of *unbatched* features and labels. + feature_columns: An iterable containing all the feature columns used by + the model. All items in the set should be instances of classes derived + from `FeatureColumn`. + model_dir: Directory to save model parameters, graph and etc. This can + also be used to load checkpoints from the directory into a estimator + to continue training a previously saved model. + n_classes: number of label classes. Default is binary classification. + Multiclass support is not yet implemented. + weight_column: A string or a `_NumericColumn` created by + `tf.feature_column.numeric_column` defining feature column representing + weights. It is used to downweight or boost examples during training. It + will be multiplied by the loss of the example. If it is a string, it is + used as a key to fetch weight tensor from the `features`. If it is a + `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, + then weight_column.normalizer_fn is applied on it to get weight tensor. + label_vocabulary: A list of strings represents possible label values. If + given, labels must be string type and have any value in + `label_vocabulary`. If it is not given, that means labels are + already encoded as integer or float within [0, 1] for `n_classes=2` and + encoded as integer values in {0, 1,..., n_classes-1} for `n_classes`>2 . + Also there will be errors if vocabulary is not provided and labels are + string. + n_trees: number trees to be created. + max_depth: maximum depth of the tree to grow. + learning_rate: shrinkage parameter to be used when a tree added to the + model. + l1_regularization: regularization multiplier applied to the absolute + weights of the tree leafs. + l2_regularization: regularization multiplier applied to the square weights + of the tree leafs. + tree_complexity: regularization factor to penalize trees with more leaves. + config: `RunConfig` object to configure the runtime settings. + train_hooks: a list of Hook instances to be passed to estimator.train(). + + Returns: + a `BoostedTreesClassifier` instance created with the given arguments and + trained with the data loaded up on memory from the input_fn. + + Raises: + ValueError: when wrong arguments are given or unsupported functionalities + are requested. + """ + # pylint: disable=protected-access + # TODO(nponomareva): Support multi-class cases. + if n_classes == canned_boosted_trees._HOLD_FOR_MULTI_CLASS_SUPPORT: + n_classes = 2 + head, closed_form = ( + canned_boosted_trees._create_classification_head_and_closed_form( + n_classes, weight_column, label_vocabulary=label_vocabulary)) + + # HParams for the model. + tree_hparams = canned_boosted_trees.TreeHParams( + n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, + tree_complexity) + + def _model_fn(features, labels, mode, config): + return canned_boosted_trees._bt_model_fn( + features, + labels, + mode, + head, + feature_columns, + tree_hparams, + n_batches_per_layer=1, + config=config, + closed_form_grad_and_hess_fn=closed_form, + train_in_memory=True) + + in_memory_classifier = estimator.Estimator( + model_fn=_model_fn, model_dir=model_dir, config=config) + + in_memory_classifier.train(input_fn=train_input_fn, hooks=train_hooks) + + return in_memory_classifier + # pylint: enable=protected-access + + +def boosted_trees_regressor_train_in_memory( + train_input_fn, + feature_columns, + model_dir=None, + label_dimension=canned_boosted_trees._HOLD_FOR_MULTI_DIM_SUPPORT, + weight_column=None, + n_trees=100, + max_depth=6, + learning_rate=0.1, + l1_regularization=0., + l2_regularization=0., + tree_complexity=0., + config=None, + train_hooks=None): + """Trains a boosted tree regressor with in memory dataset. + + Example: + + ```python + bucketized_feature_1 = bucketized_column( + numeric_column('feature_1'), BUCKET_BOUNDARIES_1) + bucketized_feature_2 = bucketized_column( + numeric_column('feature_2'), BUCKET_BOUNDARIES_2) + + def input_fn_train(): + dataset = create-dataset-from-training-data + # Don't use repeat or cache, since it is assumed to be one epoch + # This is either tf.data.Dataset, or a tuple of feature dict and label. + return dataset + + regressor = boosted_trees_regressor_train_in_memory( + train_input_fn, + feature_columns=[bucketized_feature_1, bucketized_feature_2], + n_trees=100, + ... + ) + + def input_fn_eval(): + ... + return dataset + + metrics = regressor.evaluate(input_fn=input_fn_eval, steps=10) + ``` + + Args: + train_input_fn: the input function returns a dataset containing a single + epoch of *unbatched* features and labels. + feature_columns: An iterable containing all the feature columns used by + the model. All items in the set should be instances of classes derived + from `FeatureColumn`. + model_dir: Directory to save model parameters, graph and etc. This can + also be used to load checkpoints from the directory into a estimator + to continue training a previously saved model. + label_dimension: Number of regression targets per example. + Multi-dimensional support is not yet implemented. + weight_column: A string or a `_NumericColumn` created by + `tf.feature_column.numeric_column` defining feature column representing + weights. It is used to downweight or boost examples during training. It + will be multiplied by the loss of the example. If it is a string, it is + used as a key to fetch weight tensor from the `features`. If it is a + `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, + then weight_column.normalizer_fn is applied on it to get weight tensor. + n_trees: number trees to be created. + max_depth: maximum depth of the tree to grow. + learning_rate: shrinkage parameter to be used when a tree added to the + model. + l1_regularization: regularization multiplier applied to the absolute + weights of the tree leafs. + l2_regularization: regularization multiplier applied to the square weights + of the tree leafs. + tree_complexity: regularization factor to penalize trees with more leaves. + config: `RunConfig` object to configure the runtime settings. + train_hooks: a list of Hook instances to be passed to estimator.train(). + + Returns: + a `BoostedTreesClassifier` instance created with the given arguments and + trained with the data loaded up on memory from the input_fn. + + Raises: + ValueError: when wrong arguments are given or unsupported functionalities + are requested. + """ + # pylint: disable=protected-access + # TODO(nponomareva): Extend it to multi-dimension cases. + if label_dimension == canned_boosted_trees._HOLD_FOR_MULTI_DIM_SUPPORT: + label_dimension = 1 + head = canned_boosted_trees._create_regression_head(label_dimension, + weight_column) + + # HParams for the model. + tree_hparams = canned_boosted_trees.TreeHParams( + n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, + tree_complexity) + + def _model_fn(features, labels, mode, config): + return canned_boosted_trees._bt_model_fn( + features, + labels, + mode, + head, + feature_columns, + tree_hparams, + n_batches_per_layer=1, + config=config, + train_in_memory=True) + + in_memory_regressor = estimator.Estimator( + model_fn=_model_fn, model_dir=model_dir, config=config) + + in_memory_regressor.train(input_fn=train_input_fn, hooks=train_hooks) + + return in_memory_regressor + # pylint: enable=protected-access diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py new file mode 100644 index 0000000000..e99a87f3b3 --- /dev/null +++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py @@ -0,0 +1,207 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests boosted_trees estimators.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.estimator.python.estimator import boosted_trees +from tensorflow.python.estimator.canned import boosted_trees as canned_boosted_trees +from tensorflow.python.estimator.inputs import numpy_io +from tensorflow.python.feature_column import feature_column +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.platform import googletest +from tensorflow.python.training import checkpoint_utils + +NUM_FEATURES = 3 + +BUCKET_BOUNDARIES = [-2., .5, 12.] # Boundaries for all the features. +INPUT_FEATURES = np.array( + [ + [12.5, 1.0, -2.001, -2.0001, -1.999], # feature_0 quantized:[3,2,0,0,1] + [2.0, -3.0, 0.5, 0.0, 0.4995], # feature_1 quantized:[2,0,2,1,1] + [3.0, 20.0, 50.0, -100.0, 102.75], # feature_2 quantized:[2,3,3,0,3] + ], + dtype=np.float32) +CLASSIFICATION_LABELS = [[0.], [1.], [1.], [0.], [0.]] +REGRESSION_LABELS = [[1.5], [0.3], [0.2], [2.], [5.]] +FEATURES_DICT = {'f_%d' % i: INPUT_FEATURES[i] for i in range(NUM_FEATURES)} + + +def _make_train_input_fn(is_classification): + """Makes train input_fn for classification/regression.""" + + def _input_fn(): + features = dict(FEATURES_DICT) + if is_classification: + labels = CLASSIFICATION_LABELS + else: + labels = REGRESSION_LABELS + return features, labels + + return _input_fn + + +class BoostedTreesEstimatorTest(test_util.TensorFlowTestCase): + + def setUp(self): + self._head = canned_boosted_trees._create_regression_head(label_dimension=1) + self._feature_columns = { + feature_column.bucketized_column( + feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), + BUCKET_BOUNDARIES) + for i in range(NUM_FEATURES) + } + + def _assert_checkpoint(self, model_dir, expected_global_step): + self.assertEqual(expected_global_step, + checkpoint_utils.load_variable(model_dir, + ops.GraphKeys.GLOBAL_STEP)) + + def testTrainAndEvaluateEstimator(self): + input_fn = _make_train_input_fn(is_classification=False) + + est = boosted_trees._BoostedTreesEstimator( + feature_columns=self._feature_columns, + n_batches_per_layer=1, + n_trees=2, + head=self._head, + max_depth=5) + + # It will stop after 10 steps because of the max depth and num trees. + num_steps = 100 + # Train for a few steps, and validate final checkpoint. + est.train(input_fn, steps=num_steps) + self._assert_checkpoint(est.model_dir, 11) + eval_res = est.evaluate(input_fn=input_fn, steps=1) + self.assertAllClose(eval_res['average_loss'], 0.913176) + + def testInferEstimator(self): + train_input_fn = _make_train_input_fn(is_classification=False) + predict_input_fn = numpy_io.numpy_input_fn( + x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) + + est = boosted_trees._BoostedTreesEstimator( + feature_columns=self._feature_columns, + n_batches_per_layer=1, + n_trees=1, + max_depth=5, + head=self._head) + + # It will stop after 5 steps because of the max depth and num trees. + num_steps = 100 + # Train for a few steps, and validate final checkpoint. + est.train(train_input_fn, steps=num_steps) + self._assert_checkpoint(est.model_dir, 6) + + predictions = list(est.predict(input_fn=predict_input_fn)) + self.assertEquals(5, len(predictions)) + self.assertAllClose([0.703549], predictions[0]['predictions']) + self.assertAllClose([0.266539], predictions[1]['predictions']) + self.assertAllClose([0.256479], predictions[2]['predictions']) + self.assertAllClose([1.088732], predictions[3]['predictions']) + self.assertAllClose([1.901732], predictions[4]['predictions']) + + +class BoostedTreesClassifierTrainInMemoryTest(test_util.TensorFlowTestCase): + + def setUp(self): + self._feature_columns = { + feature_column.bucketized_column( + feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), + BUCKET_BOUNDARIES) + for i in range(NUM_FEATURES) + } + + def _assert_checkpoint(self, model_dir, expected_global_step): + self.assertEqual(expected_global_step, + checkpoint_utils.load_variable(model_dir, + ops.GraphKeys.GLOBAL_STEP)) + + def testBinaryClassifierTrainInMemoryAndEvalAndInfer(self): + train_input_fn = _make_train_input_fn(is_classification=True) + predict_input_fn = numpy_io.numpy_input_fn( + x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) + + est = boosted_trees.boosted_trees_classifier_train_in_memory( + train_input_fn=train_input_fn, + feature_columns=self._feature_columns, + n_trees=1, + max_depth=5) + # It will stop after 5 steps because of the max depth and num trees. + self._assert_checkpoint(est.model_dir, 6) + + # Check eval. + eval_res = est.evaluate(input_fn=train_input_fn, steps=1) + self.assertAllClose(eval_res['accuracy'], 1.0) + + # Check predict that all labels are correct. + predictions = list(est.predict(input_fn=predict_input_fn)) + self.assertEquals(5, len(predictions)) + self.assertAllClose([0], predictions[0]['class_ids']) + self.assertAllClose([1], predictions[1]['class_ids']) + self.assertAllClose([1], predictions[2]['class_ids']) + self.assertAllClose([0], predictions[3]['class_ids']) + self.assertAllClose([0], predictions[4]['class_ids']) + + +class BoostedTreesRegressorTrainInMemoryTest(test_util.TensorFlowTestCase): + + def setUp(self): + self._feature_columns = { + feature_column.bucketized_column( + feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), + BUCKET_BOUNDARIES) + for i in range(NUM_FEATURES) + } + + def _assert_checkpoint(self, model_dir, expected_global_step): + self.assertEqual(expected_global_step, + checkpoint_utils.load_variable(model_dir, + ops.GraphKeys.GLOBAL_STEP)) + + def testRegressorTrainInMemoryAndEvalAndInfer(self): + train_input_fn = _make_train_input_fn(is_classification=False) + predict_input_fn = numpy_io.numpy_input_fn( + x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) + + est = boosted_trees.boosted_trees_regressor_train_in_memory( + train_input_fn=train_input_fn, + feature_columns=self._feature_columns, + n_trees=1, + max_depth=5) + # It will stop after 5 steps because of the max depth and num trees. + self._assert_checkpoint(est.model_dir, 6) + + # Check eval. + eval_res = est.evaluate(input_fn=train_input_fn, steps=1) + self.assertAllClose(eval_res['average_loss'], 2.2136638) + + # Validate predictions. + predictions = list(est.predict(input_fn=predict_input_fn)) + self.assertEquals(5, len(predictions)) + self.assertAllClose([0.703549], predictions[0]['predictions']) + self.assertAllClose([0.266539], predictions[1]['predictions']) + self.assertAllClose([0.256479], predictions[2]['predictions']) + self.assertAllClose([1.088732], predictions[3]['predictions']) + self.assertAllClose([1.901732], predictions[4]['predictions']) + + +if __name__ == '__main__': + googletest.main() diff --git a/tensorflow/contrib/makefile/tf_op_files.txt b/tensorflow/contrib/makefile/tf_op_files.txt index 5a812af4e9..15786291ed 100644 --- a/tensorflow/contrib/makefile/tf_op_files.txt +++ b/tensorflow/contrib/makefile/tf_op_files.txt @@ -228,6 +228,11 @@ tensorflow/core/kernels/cast_op_impl_int64.cc tensorflow/core/kernels/cast_op_impl_int8.cc tensorflow/core/kernels/cast_op_impl_uint16.cc tensorflow/core/kernels/cast_op_impl_uint8.cc +tensorflow/core/kernels/boosted_trees/prediction_ops.cc +tensorflow/core/kernels/boosted_trees/resource_ops.cc +tensorflow/core/kernels/boosted_trees/resources.cc +tensorflow/core/kernels/boosted_trees/stats_ops.cc +tensorflow/core/kernels/boosted_trees/training_ops.cc tensorflow/core/kernels/bias_op.cc tensorflow/core/kernels/bcast_ops.cc tensorflow/core/kernels/batch_norm_op.cc @@ -285,6 +290,7 @@ tensorflow/core/ops/data_flow_ops.cc tensorflow/core/ops/ctc_ops.cc tensorflow/core/ops/control_flow_ops.cc tensorflow/core/ops/candidate_sampling_ops.cc +tensorflow/core/ops/boosted_trees_ops.cc tensorflow/core/ops/array_ops.cc tensorflow/core/ops/array_grad.cc tensorflow/core/kernels/spacetobatch_functor.cc diff --git a/tensorflow/contrib/makefile/tf_proto_files.txt b/tensorflow/contrib/makefile/tf_proto_files.txt index d569bde637..1f254692d7 100644 --- a/tensorflow/contrib/makefile/tf_proto_files.txt +++ b/tensorflow/contrib/makefile/tf_proto_files.txt @@ -18,6 +18,7 @@ tensorflow/core/protobuf/device_properties.proto tensorflow/core/protobuf/rewriter_config.proto tensorflow/core/protobuf/tensor_bundle.proto tensorflow/core/lib/core/error_codes.proto +tensorflow/core/kernels/boosted_trees/boosted_trees.proto tensorflow/core/framework/versions.proto tensorflow/core/framework/variable.proto tensorflow/core/framework/types.proto diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index b8dbd90ab8..614e06cf83 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -629,6 +629,7 @@ tf_gen_op_libs( op_lib_names = [ "batch_ops", "bitwise_ops", + "boosted_trees_ops", "candidate_sampling_ops", "checkpoint_ops", "control_flow_ops", @@ -741,6 +742,7 @@ cc_library( ":audio_ops_op_lib", ":batch_ops_op_lib", ":bitwise_ops_op_lib", + ":boosted_trees_ops_op_lib", ":candidate_sampling_ops_op_lib", ":checkpoint_ops_op_lib", ":control_flow_ops_op_lib", @@ -882,6 +884,7 @@ cc_library( "//tensorflow/core/kernels:audio", "//tensorflow/core/kernels:batch_kernels", "//tensorflow/core/kernels:bincount_op", + "//tensorflow/core/kernels:boosted_trees_ops", "//tensorflow/core/kernels:candidate_sampler_ops", "//tensorflow/core/kernels:checkpoint_ops", "//tensorflow/core/kernels:control_flow_ops", diff --git a/tensorflow/core/api_def/base_api/api_def_BoostedTreesCalculateBestGainsPerFeature.pbtxt b/tensorflow/core/api_def/base_api/api_def_BoostedTreesCalculateBestGainsPerFeature.pbtxt new file mode 100644 index 0000000000..b1921e3507 --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_BoostedTreesCalculateBestGainsPerFeature.pbtxt @@ -0,0 +1,87 @@ +op { + graph_op_name: "BoostedTreesCalculateBestGainsPerFeature" + visibility: HIDDEN + in_arg { + name: "node_id_range" + description: <
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc1GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
+ + @@ -469,6 +471,7 @@ Stack Overflow and specify the `tensorflow` tag. **Mac**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6GCC 4.8Bazel 0.10.0N/AN/A
tensorflow_gpu-1.7.0rc1GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.6.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.0N/AN/A
tensorflow_gpu-1.6.0GPU2.7, 3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow-1.5.0CPU2.7, 3.3-3.6GCC 4.8Bazel 0.8.0N/AN/A
+ @@ -483,6 +486,8 @@ Stack Overflow and specify the `tensorflow` tag. **Windows**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU2.7, 3.3-3.6Clang from xcodeBazel 0.10.1N/AN/A
tensorflow-1.6.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.5.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.8.1N/AN/A
tensorflow-1.4.0CPU2.7, 3.3-3.6Clang from xcodeBazel 0.5.4N/AN/A
+ + diff --git a/tensorflow/docs_src/mobile/optimizing.md b/tensorflow/docs_src/mobile/optimizing.md index ca9cb043e9..778e4d3a62 100644 --- a/tensorflow/docs_src/mobile/optimizing.md +++ b/tensorflow/docs_src/mobile/optimizing.md @@ -233,6 +233,8 @@ order by how long they took. From left to right, the columns are: - The cumulative total time of this and the previous ops in the table. This is handy for understanding what the distribution of work is across the layers, to see if just a few of the nodes are taking up most of the time. + +- The amount of memory consumed by outputs of this type of op. - Name of the node. diff --git a/tensorflow/docs_src/mobile/prepare_models.md b/tensorflow/docs_src/mobile/prepare_models.md index 360ee302aa..8b22c04d87 100644 --- a/tensorflow/docs_src/mobile/prepare_models.md +++ b/tensorflow/docs_src/mobile/prepare_models.md @@ -60,7 +60,7 @@ and serialized as protocol buffers: the `NodeDef`, so if all the `Variable` weights are converted to `Const` nodes, then we only need a single `GraphDef` file to hold the model architecture and the weights. Freezing the graph handles the process of loading the - checkpoints, and then converts all Consts to Variables. You can then load the + checkpoints, and then converts all Variables to Consts. You can then load the resulting file in a single call, without having to restore variable values from checkpoints. One thing to watch out for with `GraphDef` files is that sometimes they’re stored in text format for easy inspection. These versions diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 2cc3c48c3c..b05d87635f 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -1067,7 +1067,7 @@ py_test( py_test( name = "framework_importer_test", - size = "medium", + size = "large", srcs = ["framework/importer_test.py"], main = "framework/importer_test.py", srcs_version = "PY2AND3", diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index d0ba8020c1..64c1760d5e 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -315,21 +315,39 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf_4, np.asarray(x_np)[:, ::-1]) self.assertAllEqual(x_tf_5, np.asarray(x_np)[::-1, ::-1]) + # This test covers the axis validation in the shape function + # (no eval()) + def testInvalidAxis(self): + x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [-30]) + with self.assertRaisesRegexp(ValueError, + "is out of valid range"): + array_ops.reverse_v2(x_np, [2]) + with self.assertRaisesRegexp(ValueError, + "axis 0 specified more than once"): + array_ops.reverse_v2(x_np, [0, -2]) + # This is the version of reverse that uses axis indices rather than # bool tensors # TODO(b/32254538): Change this test to use array_ops.reverse + # + # Note: this test passes placeholder as constant axis is validated + # in shape function (see testInvalidAxis) def testInvalid(self): x_np = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) + axis = array_ops.placeholder(dtypes.int32) with self.test_session(): with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [-30]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [-30]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "is out of valid range"): - array_ops.reverse_v2(x_np, [2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [2]}) with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "axis 0 specified more than once"): - array_ops.reverse_v2(x_np, [0, -2]).eval() + array_ops.reverse_v2(x_np, axis).eval(feed_dict={axis: [0, -2]}) def testReverse1DimAuto(self): for dtype in [ @@ -890,7 +908,7 @@ class StridedSliceAssignChecker(object): var = resource_variable_ops.ResourceVariable(self.x) else: var = variables.Variable(self.x) - sess.run(variables.initialize_variables([var])) + sess.run(variables.variables_initializer([var])) val = sess.run(var[index].assign(value)) # val_copy is used to check that tf.assign works equivalently to the # assign method above. diff --git a/tensorflow/python/kernel_tests/testdata/BUILD b/tensorflow/python/kernel_tests/testdata/BUILD index a4a0dfc139..45264c773a 100644 --- a/tensorflow/python/kernel_tests/testdata/BUILD +++ b/tensorflow/python/kernel_tests/testdata/BUILD @@ -1,7 +1,7 @@ # Data files for kernel tests. package( - default_visibility = ["//tensorflow:internal"], + default_visibility = ["//visibility:public"], ) licenses(["notice"]) # Apache 2.0 diff --git a/tensorflow/python/kernel_tests/xent_op_test.py b/tensorflow/python/kernel_tests/xent_op_test.py index e3e120a4eb..60c726d54c 100644 --- a/tensorflow/python/kernel_tests/xent_op_test.py +++ b/tensorflow/python/kernel_tests/xent_op_test.py @@ -18,10 +18,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import itertools +import sys + import numpy as np +from tensorflow.python.client import session from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import gradient_checker from tensorflow.python.ops import gradients_impl @@ -88,7 +94,7 @@ class XentTest(test.TestCase): 4.]]]).astype(dtype) np_labels = np.array([[[0., 0., 0., 1.]], [[0., .5, .5, 0.]]]).astype(dtype) - self.assertRaisesRegexp(ValueError, "must be rank 2", + self.assertRaisesRegexp(ValueError, "rank 2, but is rank 3", gen_nn_ops.softmax_cross_entropy_with_logits, np_features, np_labels) @@ -128,6 +134,24 @@ class XentTest(test.TestCase): self.assertAllClose( np.array([1.3862, 1.9401]), np_loss, rtol=1.e-3, atol=1.e-3) + def testShapeBroadcast(self): + np_f = np.array([[1., 2., 3., 4.], + [1., 2., 3., 4.]]).astype(np.float32) + np_l = np.array([[0., 0., 0., 1.], + [0., .5, .5, 0.]]).astype(np.float32) + np_loss, np_backprop = self._npXent(np_f, np_l) + tf_f = constant_op.constant( + np.array([[1., 2., 3., 4.]]).astype(np.float32)) + tf_l = constant_op.constant( + np.array([[0., 0., 0., 1.], [0., .5, .5, 0.]]).astype(np.float32)) + for use_gpu in [False, True]: + with self.test_session(use_gpu=use_gpu) as sess: + loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( + tf_f, tf_l) + tf_loss, tf_backprop = sess.run([loss, backprop]) + self.assertAllCloseAccordingToType(np_loss, tf_loss) + self.assertAllCloseAccordingToType(np_backprop, tf_backprop) + def testShapeMismatch(self): with self.test_session(): with self.assertRaises(ValueError): @@ -260,5 +284,60 @@ class XentTest(test.TestCase): self.assertAllEqual(np_loss, tf_loss) +class XentBenchmark(test.Benchmark): + + def benchmarkZeroDimension(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "zero_dimension_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + logits = array_ops.zeros([0, 2, 4], dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + def benchmarkSingleClass(self): + for (m, n, p, use_gpu) in itertools.product( + [128], + [10, 100, 1000, 10000, 100000], + [0.001, 0.01, 0.5, 0.99, 1.0], + [False]): + k = int(p * n) + if k == 0: + continue + name = "single_class_m_%d_n_%d_k_%g_use_gpu_%s" % (m, n, k, use_gpu) + device = "/%s:0" % ("gpu" if use_gpu else "cpu") + with ops.Graph().as_default(): + with ops.device(device): + labels = constant_op.constant([[1.], [-1.], [0.]], + dtype=dtypes.float32) + logits = constant_op.constant([[-1.], [0.], [1.]], + dtype=dtypes.float32) + op = nn_ops.softmax_cross_entropy_with_logits( + labels=labels, logits=logits) + with session.Session() as sess: + r = self.run_op_benchmark(sess, op, min_iters=100, name=name) + gb_processed_input = m * n / 1.0e9 + throughput = gb_processed_input / r["wall_time"] + print("Benchmark: %s \t wall_time: %0.03g s \t " + "Throughput: %0.03g GB/s" % (name, r["wall_time"], throughput)) + sys.stdout.flush() + + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/layers/convolutional.py b/tensorflow/python/layers/convolutional.py index 74e7c63fb3..2d99b1688f 100644 --- a/tensorflow/python/layers/convolutional.py +++ b/tensorflow/python/layers/convolutional.py @@ -180,6 +180,8 @@ class _Conv(base.Layer): # bias_add when computing gradients. To use bias_add, we collapse Z # and Y into a single dimension to obtain a 4D input tensor. outputs_shape = outputs.shape.as_list() + if outputs_shape[0] is None: + outputs_shape[0] = -1 outputs_4d = array_ops.reshape(outputs, [outputs_shape[0], outputs_shape[1], outputs_shape[2] * outputs_shape[3], diff --git a/tensorflow/python/layers/convolutional_test.py b/tensorflow/python/layers/convolutional_test.py index 160e732b67..cdb42f5bd1 100644 --- a/tensorflow/python/layers/convolutional_test.py +++ b/tensorflow/python/layers/convolutional_test.py @@ -325,6 +325,12 @@ class ConvTest(test.TestCase): self.assertEqual(conv3d.kernel_constraint, k_constraint) self.assertEqual(conv3d.bias_constraint, b_constraint) + def testConv3DChannelsFirst(self): + # Test case for GitHub issue 15655 + images = array_ops.placeholder( + dtype=dtypes.float32, shape=[None, 1, 32, 32, 32]) + conv_layers.conv3d(images, 32, 9, data_format='channels_first') + @test_util.with_c_api class SeparableConv1DTest(test.TestCase): diff --git a/tensorflow/python/ops/linalg_ops.py b/tensorflow/python/ops/linalg_ops.py index 5b4fb4f7c8..170861b43f 100644 --- a/tensorflow/python/ops/linalg_ops.py +++ b/tensorflow/python/ops/linalg_ops.py @@ -429,7 +429,7 @@ def svd(tensor, full_matrices=False, compute_uv=True, name=None): u, s, v_adj = np.linalg.svd(a, full_matrices=False) np_a_approx = np.dot(u, np.dot(np.diag(s), v_adj)) # tf_a_approx and np_a_approx should be numerically close. - ```` + ``` @end_compatibility """ s, u, v = gen_linalg_ops.svd( diff --git a/tensorflow/python/training/monitored_session.py b/tensorflow/python/training/monitored_session.py index 6c5c9e01a7..4ce6f6d002 100644 --- a/tensorflow/python/training/monitored_session.py +++ b/tensorflow/python/training/monitored_session.py @@ -281,13 +281,14 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name scaffold=None, hooks=None, chief_only_hooks=None, - save_checkpoint_secs=600, + save_checkpoint_secs=USE_DEFAULT, save_summaries_steps=USE_DEFAULT, save_summaries_secs=USE_DEFAULT, config=None, stop_grace_period_secs=120, log_step_count_steps=100, - max_wait_secs=7200): + max_wait_secs=7200, + save_checkpoint_steps=USE_DEFAULT): """Creates a `MonitoredSession` for training. For a chief, this utility sets proper session initializer/restorer. It also @@ -310,8 +311,10 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name chief_only_hooks: list of `SessionRunHook` objects. Activate these hooks if `is_chief==True`, ignore otherwise. save_checkpoint_secs: The frequency, in seconds, that a checkpoint is saved - using a default checkpoint saver. If `save_checkpoint_secs` is set to - `None`, then the default checkpoint saver isn't used. + using a default checkpoint saver. If both `save_checkpoint_steps` and + `save_checkpoint_secs` are set to `None`, then the default checkpoint + saver isn't used. If both are provided, then only `save_checkpoint_secs` + is used. Default 600. save_summaries_steps: The frequency, in number of global steps, that the summaries are written to disk using a default summary saver. If both `save_summaries_steps` and `save_summaries_secs` are set to `None`, then @@ -330,6 +333,11 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name become available. This should be kept relatively short to help detect incorrect code, but sometimes may need to be increased if the chief takes a while to start up. + save_checkpoint_steps: The frequency, in number of global steps, that a + checkpoint is saved using a default checkpoint saver. If both + `save_checkpoint_steps` and `save_checkpoint_secs` are set to `None`, then + the default checkpoint saver isn't used. If both are provided, then only + `save_checkpoint_secs` is used. Default not enabled. Returns: A `MonitoredSession` object. @@ -342,6 +350,15 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name elif save_summaries_steps == USE_DEFAULT: save_summaries_steps = None + if (save_checkpoint_steps == USE_DEFAULT and + save_checkpoint_secs == USE_DEFAULT): + save_checkpoint_steps = None + save_checkpoint_secs = 600 + elif save_checkpoint_secs == USE_DEFAULT: + save_checkpoint_secs = None + elif save_checkpoint_steps == USE_DEFAULT: + save_checkpoint_steps = None + scaffold = scaffold or Scaffold() if not is_chief: session_creator = WorkerSessionCreator( @@ -374,9 +391,13 @@ def MonitoredTrainingSession(master='', # pylint: disable=invalid-name save_steps=save_summaries_steps, save_secs=save_summaries_secs, output_dir=checkpoint_dir)) - if save_checkpoint_secs and save_checkpoint_secs > 0: + if (save_checkpoint_secs and save_checkpoint_secs > 0) or ( + save_checkpoint_steps and save_checkpoint_steps > 0): all_hooks.append(basic_session_run_hooks.CheckpointSaverHook( - checkpoint_dir, save_secs=save_checkpoint_secs, scaffold=scaffold)) + checkpoint_dir, + save_steps=save_checkpoint_steps, + save_secs=save_checkpoint_secs, + scaffold=scaffold)) if hooks: all_hooks.extend(hooks) diff --git a/tensorflow/python/training/monitored_session_test.py b/tensorflow/python/training/monitored_session_test.py index 159b2d5c16..3806056f01 100644 --- a/tensorflow/python/training/monitored_session_test.py +++ b/tensorflow/python/training/monitored_session_test.py @@ -282,6 +282,42 @@ class MonitoredTrainingSessionTest(test.TestCase): is_chief=True, checkpoint_dir=logdir) as session: self.assertEqual(2, session.run(gstep)) + def test_save_checkpoint_steps(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_steps') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_steps=100, + log_step_count_steps=10) as session: + for _ in range(100): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(100, session.run(gstep)) + + def test_save_checkpoint_secs(self): + logdir = _test_dir(self.get_temp_dir(), 'test_save_checkpoint_secs') + with ops.Graph().as_default(): + gstep = variables_lib.get_or_create_global_step() + new_gstep = state_ops.assign_add(gstep, 1) + with monitored_session.MonitoredTrainingSession( + is_chief=True, + checkpoint_dir=logdir, + save_checkpoint_secs=0.1, + log_step_count_steps=10) as session: + session.run(new_gstep) + time.sleep(0.2) + for _ in range(10): + session.run(new_gstep) + # A restart will find the checkpoint and recover automatically. + with monitored_session.MonitoredTrainingSession( + is_chief=True, checkpoint_dir=logdir) as session: + self.assertEqual(11, session.run(gstep)) + def test_summaries_steps(self): logdir = _test_dir(self.get_temp_dir(), 'test_summaries_steps') with ops.Graph().as_default(): diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 2d3cb415fe..fcc57d506e 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -22,6 +22,7 @@ load( load( "//third_party/mkl:build_defs.bzl", "if_mkl", + "if_mkl_lnx_x64" ) def register_extension_info(**kwargs): @@ -202,7 +203,8 @@ def tf_copts(android_optimization_level_override="-O2", is_external=False): "-ftemplate-depth=900"]) + if_cuda(["-DGOOGLE_CUDA=1"]) + if_tensorrt(["-DGOOGLE_TENSORRT=1"]) - + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML", "-fopenmp",]) + + if_mkl(["-DINTEL_MKL=1", "-DEIGEN_USE_VML"]) + + if_mkl_lnx_x64(["-fopenmp"]) + if_android_arm(["-mfpu=neon"]) + if_linux_x86_64(["-msse3"]) + if_ios_x86_64(["-msse4.1"]) diff --git a/tensorflow/tools/api/golden/tensorflow.train.pbtxt b/tensorflow/tools/api/golden/tensorflow.train.pbtxt index c75ee474aa..bec72e1e60 100644 --- a/tensorflow/tools/api/golden/tensorflow.train.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.train.pbtxt @@ -238,7 +238,7 @@ tf_module { } member_method { name: "MonitoredTrainingSession" - argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'600\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\'], " + argspec: "args=[\'master\', \'is_chief\', \'checkpoint_dir\', \'scaffold\', \'hooks\', \'chief_only_hooks\', \'save_checkpoint_secs\', \'save_summaries_steps\', \'save_summaries_secs\', \'config\', \'stop_grace_period_secs\', \'log_step_count_steps\', \'max_wait_secs\', \'save_checkpoint_steps\'], varargs=None, keywords=None, defaults=[\'\', \'True\', \'None\', \'None\', \'None\', \'None\', \'\', \'\', \'\', \'None\', \'120\', \'100\', \'7200\', \'\'], " } member_method { name: "NewCheckpointReader" diff --git a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh index e1b56b9a25..7d471b4703 100755 --- a/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh +++ b/tensorflow/tools/ci_build/osx/libtensorflow_cpu.sh @@ -31,5 +31,5 @@ export TF_NEED_OPENCL_SYCL=0 export TF_NEED_MKL=0 export COMPUTECPP_PATH="/usr/local" -export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" build_libtensorflow_tarball "-cpu-darwin-$(uname -m)" diff --git a/tensorflow/tools/docker/Dockerfile.devel b/tensorflow/tools/docker/Dockerfile.devel index 22c73c3fe1..11f476d12c 100644 --- a/tensorflow/tools/docker/Dockerfile.devel +++ b/tensorflow/tools/docker/Dockerfile.devel @@ -70,7 +70,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # TODO(craigcitro): Don't install the pip package, since it makes it # more difficult to experiment with local changes. Instead, just add diff --git a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl index 3690e7dfe5..037d13116e 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl +++ b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl @@ -3,7 +3,7 @@ FROM tensorflow/tensorflow:latest-devel LABEL maintainer="Clayne Robison" # These arguments are parameterized. Use --build-args to override. -ARG TF_BRANCH=r1.6 +ARG TF_BRANCH=r1.7 ARG WHL_DIR=/whl RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/tensorflow/tools/docker/Dockerfile.devel-gpu b/tensorflow/tools/docker/Dockerfile.devel-gpu index 69ba340f92..1fcb6428b2 100644 --- a/tensorflow/tools/docker/Dockerfile.devel-gpu +++ b/tensorflow/tools/docker/Dockerfile.devel-gpu @@ -79,7 +79,7 @@ RUN mkdir /bazel && \ # Download and build TensorFlow. WORKDIR /tensorflow -RUN git clone --branch=r1.6 --depth=1 https://github.com/tensorflow/tensorflow.git . +RUN git clone --branch=r1.7 --depth=1 https://github.com/tensorflow/tensorflow.git . # Configure the build for our CUDA configuration. ENV CI_BUILD_PYTHON python diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 3fbdb5cacd..0ede8c6370 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -138,7 +138,6 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]), outs = ["include/tensorflow/c/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", @@ -176,7 +175,6 @@ genrule( "@zlib_archive//:zlib.h", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]), outs = ["include/tensorflow/jni/LICENSE"], cmd = "$(location :concat_licenses.sh) $(SRCS) >$@", diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index dd75eda231..62fec2c402 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -127,7 +127,6 @@ filegroup( "@org_python_pypi_backports_weakref//:LICENSE", ] + if_mkl([ "//third_party/mkl:LICENSE", - "@mkl//:LICENSE", ]) + if_not_windows([ "@nccl_archive//:LICENSE.txt", ]) + tf_additional_license_deps(), diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index e0152da4df..365e8d6b08 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -29,7 +29,7 @@ from setuptools.dist import Distribution # This version string is semver compatible, but incompatible with pip. # For pip, we will remove all '-' characters from this string, and use the # result for pip. -_VERSION = '1.6.0' +_VERSION = '1.7.0-rc1' REQUIRED_PACKAGES = [ 'absl-py >= 0.1.6', @@ -39,7 +39,7 @@ REQUIRED_PACKAGES = [ 'numpy >= 1.13.3', 'six >= 1.10.0', 'protobuf >= 3.4.0', - 'tensorboard >= 1.6.0, < 1.7.0', + 'tensorboard >= 1.7.0, < 1.8.0', 'termcolor >= 1.1.0', ] @@ -62,7 +62,7 @@ else: if 'tf_nightly' in project_name: for i, pkg in enumerate(REQUIRED_PACKAGES): if 'tensorboard' in pkg: - REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.7.0a0, < 1.8.0a0' + REQUIRED_PACKAGES[i] = 'tb-nightly >= 1.8.0a0, < 1.9.0a0' break # weakref.finalize and enum were introduced in Python 3.4 diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 0e31358236..ac6380dd3e 100644 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -15,6 +15,11 @@ load("@io_bazel_rules_closure//closure/private:java_import_external.bzl", "java_ load("@io_bazel_rules_closure//closure:defs.bzl", "filegroup_external") +# Sanitize a dependency so that it works correctly from code that includes +# TensorFlow as a submodule. +def clean_dep(dep): + return str(Label(dep)) + # If TensorFlow is linked as a submodule. # path_prefix is no longer used. # tf_repo_name is thought to be under consideration. @@ -32,17 +37,37 @@ def tf_workspace(path_prefix="", tf_repo_name=""): arm_compiler_configure( name="local_config_arm_compiler", remote_config_repo="../arm_compiler", - build_file = str(Label("//third_party/toolchains/cpus/arm:BUILD"))) + build_file = clean_dep("//third_party/toolchains/cpus/arm:BUILD")) mkl_repository( - name = "mkl", + name = "mkl_linux", + urls = [ + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_lnx_2018.0.1.20171227.tgz", + ], + sha256 = "feacc3d82565c1231470359b42c696236fae873704e0b013436afba5fd4fd30f", + strip_prefix = "mklml_lnx_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") + ) + mkl_repository( + name = "mkl_windows", + urls = [ + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_win_2018.0.1.20171227.zip" + ], + sha256 = "24bae8d7b22b431a654acadea43f2243c46ae6b1e5a73a4a936825f31d284ee4", + strip_prefix = "mklml_win_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") + ) + mkl_repository( + name = "mkl_darwin", urls = [ - "https://mirror.bazel.build/github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", - "https://github.com/01org/mkl-dnn/releases/download/v0.11/mklml_lnx_2018.0.1.20171007.tgz", + "https://mirror.bazel.build/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz", + "https://github.com/intel/mkl-dnn/releases/download/v0.12/mklml_mac_2018.0.1.20171227.tgz" ], - sha256 = "6b07cb7e5451db67c2e31e785ae458b18f7f363c60a61685488f69e9ae7199d4", - strip_prefix = "mklml_lnx_2018.0.1.20171007", - build_file = str(Label("//third_party/mkl:mkl.BUILD")), + sha256 = "0e954ec6fd3dc5e37f64c4043f6b5613dd687558da3df1028b3b7c29ff5cf77f", + strip_prefix = "mklml_mac_2018.0.1.20171227", + build_file = clean_dep("//third_party/mkl:mkl.BUILD") ) if path_prefix: @@ -52,12 +77,12 @@ def tf_workspace(path_prefix="", tf_repo_name=""): tf_http_archive( name = "mkl_dnn", urls = [ - "https://mirror.bazel.build/github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", - "https://github.com/01org/mkl-dnn/archive/e0bfcaa7fcb2b1e1558f5f0676933c1db807a729.tar.gz", + "https://mirror.bazel.build/github.com/intel/mkl-dnn/archive/v0.12.tar.gz", + "https://github.com/intel/mkl-dnn/archive/v0.12.tar.gz", ], - sha256 = "02e244f63dd95402691a361392504c143eede9a89043426f174836638a9cbf09", - strip_prefix = "mkl-dnn-e0bfcaa7fcb2b1e1558f5f0676933c1db807a729", - build_file = str(Label("//third_party/mkl_dnn:mkldnn.BUILD")), + sha256 = "86fa2a8c12a56e3b725945acedeaa82492746be02545aba6d710f097e013e19e", + strip_prefix = "mkl-dnn-0.12", + build_file = clean_dep("//third_party/mkl_dnn:mkldnn.BUILD"), ) tf_http_archive( @@ -68,7 +93,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "5996380e3e8b981f55d1c8d58e709c00dbb4806ba367be75d0925a68cc2f6478", strip_prefix = "abseil-cpp-720c017e30339fd1786ce4aac68bc8559736e53f", - build_file = str(Label("//third_party:com_google_absl.BUILD")), + build_file = clean_dep("//third_party:com_google_absl.BUILD"), ) tf_http_archive( @@ -79,8 +104,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "791b836cacd03e20bae5bdd25f1c4a5505a0a9975ba94a61eb4e2631fbd1d53a", strip_prefix = "eigen-eigen-6913f0cf7d06", - build_file = str(Label("//third_party:eigen.BUILD")), - patch_file = str(Label("//third_party:eigen_fix_cuda_compilation.patch")) + build_file = clean_dep("//third_party:eigen.BUILD"), + patch_file = clean_dep("//third_party:eigen_fix_cuda_compilation.patch") ) tf_http_archive( @@ -93,7 +118,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # remove the whitelist entry in third_party/repo.bzl. # "https://github.com/raspberrypi/tools/archive/0e906ebc527eab1cdbf7adabff5b474da9562e9f.tar.gz", ], - build_file = str(Label("//:arm_compiler.BUILD")), + build_file = clean_dep("//:arm_compiler.BUILD"), ) tf_http_archive( @@ -104,7 +129,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ade869c3f42f23b5263c7d594aa3c7e5e61ac6a3afcaf5d6e42899d2a7986ce", strip_prefix = "libxsmm-1.8.1", - build_file = str(Label("//third_party:libxsmm.BUILD")), + build_file = clean_dep("//third_party:libxsmm.BUILD"), ) tf_http_archive( @@ -117,7 +142,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "932075525642b04ac6f1b50589f1df5cd72ec2f448b721fd32234cf183f0e755", strip_prefix = "or-tools-253f7955c6a1fd805408fba2e42ac6d45b312d15/src", - build_file = str(Label("//third_party:ortools.BUILD")), + build_file = clean_dep("//third_party:ortools.BUILD"), ) tf_http_archive( @@ -149,7 +174,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6560547c63e4af82b0f202cb710ceabb3f21347a4b996db565a411da5b17aba0", strip_prefix = "farmhash-816a4ae622e964763ca0862d9dbd19324a1eaf45", - build_file = str(Label("//third_party:farmhash.BUILD")), + build_file = clean_dep("//third_party:farmhash.BUILD"), ) tf_http_archive( @@ -160,7 +185,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "0f30a15b1566d93f146c8d149878a06e91d9bb7ec2cfd76906df62a82be4aac9", strip_prefix = "highwayhash-dfcb97ca4fe9277bf9dc1802dd979b071896453b", - build_file = str(Label("//third_party:highwayhash.BUILD")), + build_file = clean_dep("//third_party:highwayhash.BUILD"), ) tf_http_archive( @@ -171,7 +196,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "00b0891c678c065446ca59bcee64719d0096d54d6886e6e472aeee2e170ae324", strip_prefix = "nasm-2.12.02", - build_file = str(Label("//third_party:nasm.BUILD")), + build_file = clean_dep("//third_party:nasm.BUILD"), ) tf_http_archive( @@ -182,7 +207,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "c15a9607892113946379ccea3ca8b85018301b200754f209453ab21674268e77", strip_prefix = "libjpeg-turbo-1.5.1", - build_file = str(Label("//third_party/jpeg:jpeg.BUILD")), + build_file = clean_dep("//third_party/jpeg:jpeg.BUILD"), ) tf_http_archive( @@ -193,7 +218,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "716c59c7dfc808a4c368f8ada526932be72b2fcea11dd85dc9d88b1df1dfe9c2", strip_prefix = "libpng-1.2.53", - build_file = str(Label("//third_party:png.BUILD")), + build_file = clean_dep("//third_party:png.BUILD"), ) tf_http_archive( @@ -204,7 +229,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "208780b3616f9de0aeb50822b7a8f5482f6515193859e91ed61637be6ad74fd4", strip_prefix = "sqlite-amalgamation-3200000", - build_file = str(Label("//third_party:sqlite.BUILD")), + build_file = clean_dep("//third_party:sqlite.BUILD"), ) tf_http_archive( @@ -215,7 +240,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "34a7377ba834397db019e8eb122e551a49c98f49df75ec3fcc92b9a794a4f6d1", strip_prefix = "giflib-5.1.4", - build_file = str(Label("//third_party:gif.BUILD")), + build_file = clean_dep("//third_party:gif.BUILD"), ) tf_http_archive( @@ -226,7 +251,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a", strip_prefix = "six-1.10.0", - build_file = str(Label("//third_party:six.BUILD")), + build_file = clean_dep("//third_party:six.BUILD"), ) tf_http_archive( @@ -237,7 +262,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "ff6d2e2962d834acb125cc4dcc80c54a8c17c253f4cc9d9c43b5102a560bb75d", strip_prefix = "astor-0.6.2", - build_file = str(Label("//third_party:astor.BUILD")), + build_file = clean_dep("//third_party:astor.BUILD"), ) tf_http_archive( @@ -248,7 +273,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "7068908321ecd2774f145193c4b34a11305bd104b4551b09273dfd1d6a374930", strip_prefix = "gast-0.2.0", - build_file = str(Label("//third_party:gast.BUILD")), + build_file = clean_dep("//third_party:gast.BUILD"), ) tf_http_archive( @@ -259,7 +284,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b", strip_prefix = "termcolor-1.1.0", - build_file = str(Label("//third_party:termcolor.BUILD")), + build_file = clean_dep("//third_party:termcolor.BUILD"), ) tf_http_archive( @@ -280,7 +305,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "8813bf712a66b3d8b85dc289e1104ed220f1878cf981e2fe756dfaabe9a82892", strip_prefix = "backports.weakref-1.0rc1/src", - build_file = str(Label("//third_party:backports_weakref.BUILD")), + build_file = clean_dep("//third_party:backports_weakref.BUILD"), ) tf_http_archive( @@ -291,7 +316,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2dadd04a2802de27e0fe5a19b76538f6da9d39ff244036afa00c1bba754de5ee", strip_prefix = "codegen-1.0", - build_file = str(Label("//third_party:codegen.BUILD")), + build_file = clean_dep("//third_party:codegen.BUILD"), ) filegroup_external( @@ -376,7 +401,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://ftp.exim.org/pub/pcre/pcre-8.39.tar.gz", ], strip_prefix = "pcre-8.39", - build_file = str(Label("//third_party:pcre.BUILD")), + build_file = clean_dep("//third_party:pcre.BUILD"), ) tf_http_archive( @@ -388,7 +413,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://pilotfiber.dl.sourceforge.net/project/swig/swig/swig-3.0.8/swig-3.0.8.tar.gz", ], strip_prefix = "swig-3.0.8", - build_file = str(Label("//third_party:swig.BUILD")), + build_file = clean_dep("//third_party:swig.BUILD"), ) tf_http_archive( @@ -399,7 +424,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://curl.haxx.se/download/curl-7.49.1.tar.gz", ], strip_prefix = "curl-7.49.1", - build_file = str(Label("//third_party:curl.BUILD")), + build_file = clean_dep("//third_party:curl.BUILD"), ) tf_http_archive( @@ -421,7 +446,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/antirez/linenoise/archive/c894b9e59f02203dbe4e2be657572cf88c4230c3.tar.gz", ], strip_prefix = "linenoise-c894b9e59f02203dbe4e2be657572cf88c4230c3", - build_file = str(Label("//third_party:linenoise.BUILD")), + build_file = clean_dep("//third_party:linenoise.BUILD"), ) # TODO(phawkins): currently, this rule uses an unofficial LLVM mirror. @@ -434,7 +459,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "1efbb9b05af88368be984d2f6526061d4a857181ef10f8841889a3a46869bb01", strip_prefix = "llvm-1c3cdea2f181d8e14ee184466c5fb237f1b4cda8", - build_file = str(Label("//third_party/llvm:llvm.BUILD")), + build_file = clean_dep("//third_party/llvm:llvm.BUILD"), ) tf_http_archive( @@ -445,7 +470,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "108532fb94c6f227558d45be3f3347b52539f0f58290a7bb31ec06c462d05326", strip_prefix = "lmdb-LMDB_0.9.19/libraries/liblmdb", - build_file = str(Label("//third_party:lmdb.BUILD")), + build_file = clean_dep("//third_party:lmdb.BUILD"), ) tf_http_archive( @@ -456,7 +481,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "07d34db40593d257324ec5fb9debc4dc33f29f8fb44e33a2eeb35503e61d0fe2", strip_prefix = "jsoncpp-11086dd6a7eba04289944367ca82cea71299ed70", - build_file = str(Label("//third_party:jsoncpp.BUILD")), + build_file = clean_dep("//third_party:jsoncpp.BUILD"), ) tf_http_archive( @@ -477,7 +502,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "36658cb768a54c1d4dec43c3116c27ed893e88b02ecfcb44f2166f9c0b7f2a0d", strip_prefix = "zlib-1.2.8", - build_file = str(Label("//third_party:zlib.BUILD")), + build_file = clean_dep("//third_party:zlib.BUILD"), ) tf_http_archive( @@ -487,7 +512,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "http://www.kurims.kyoto-u.ac.jp/~ooura/fft.tgz", ], sha256 = "52bb637c70b971958ec79c9c8752b1df5ff0218a4db4510e60826e0cb79b5296", - build_file = str(Label("//third_party/fft2d:fft2d.BUILD")), + build_file = clean_dep("//third_party/fft2d:fft2d.BUILD"), ) tf_http_archive( @@ -498,7 +523,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2f7504c73d85bac842e893340333be8cb8561710642fc9562fccdd9d2c3fcc94", strip_prefix = "snappy-1.1.4", - build_file = str(Label("//third_party:snappy.BUILD")), + build_file = clean_dep("//third_party:snappy.BUILD"), ) tf_http_archive( @@ -509,7 +534,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "2ca86fb6179ecbff789cc67c836139c1bbc0324ed8c04643405a30bf26325176", strip_prefix = "nccl-03d856977ecbaac87e598c0c4bafca96761b9ac7", - build_file = str(Label("//third_party:nccl.BUILD")), + build_file = clean_dep("//third_party:nccl.BUILD"), ) tf_http_archive( @@ -520,8 +545,8 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "dd035d57c8f19b0b612dd6eefe6e5eebad76f506e302cccb7c2066f25a83585e", strip_prefix = "librdkafka-0.11.1", - build_file = str(Label("//third_party:kafka/BUILD")), - patch_file = str(Label("//third_party/kafka:config.patch")), + build_file = clean_dep("//third_party:kafka/BUILD"), + patch_file = clean_dep("//third_party/kafka:config.patch"), ) tf_http_archive( @@ -532,7 +557,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "b888d8ce5fc10254c3dd6c9020c7764dd53cf39cf011249d0b4deda895de1b7c", strip_prefix = "aws-sdk-cpp-1.3.15", - build_file = str(Label("//third_party:aws.BUILD")), + build_file = clean_dep("//third_party:aws.BUILD"), ) java_import_external( @@ -568,7 +593,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "3c8f25c02e806c3ce0ab5fb7da1817f89fc9732709024e2a81b6b82f7cc792a8", strip_prefix = "jemalloc-4.4.0", - build_file = str(Label("//third_party:jemalloc.BUILD")), + build_file = clean_dep("//third_party:jemalloc.BUILD"), ) java_import_external( @@ -613,7 +638,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "e0928ca4aa10ea1e0551e2d7ce4d1d7ea2d84b2abbdef082b0da84268791d0c4", strip_prefix = "pprof-c0fb62ec88c411cc91194465e54db2632845b650", - build_file = str(Label("//third_party:pprof.BUILD")), + build_file = clean_dep("//third_party:pprof.BUILD"), ) tf_http_archive( @@ -624,7 +649,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): ], sha256 = "6bfa06ab52a650ae7ee6963143a0bbc667d6504822cbd9670369b598f18c58c3", strip_prefix = "cub-1.8.0", - build_file = str(Label("//third_party:cub.BUILD")), + build_file = clean_dep("//third_party:cub.BUILD"), ) tf_http_archive( @@ -635,7 +660,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://github.com/cython/cython/archive/3732784c45cfb040a5b0936951d196f83a12ea17.tar.gz", ], strip_prefix = "cython-3732784c45cfb040a5b0936951d196f83a12ea17", - build_file = str(Label("//third_party:cython.BUILD")), + build_file = clean_dep("//third_party:cython.BUILD"), delete = ["BUILD.bazel"], ) @@ -657,7 +682,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", "https://github.com/intel/ARM_NEON_2_x86_SSE/archive/0f77d9d182265259b135dad949230ecbf1a2633d.tar.gz", ], - build_file = str(Label("//third_party:arm_neon_2_x86_sse.BUILD")), + build_file = clean_dep("//third_party:arm_neon_2_x86_sse.BUILD"), ) tf_http_archive( @@ -668,7 +693,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", "https://github.com/google/flatbuffers/archive/971a68110e4fc1bace10fcb6deeb189e7e1a34ce.tar.gz", ], - build_file = str(Label("//third_party/flatbuffers:flatbuffers.BUILD")), + build_file = clean_dep("//third_party/flatbuffers:flatbuffers.BUILD"), ) tf_http_archive( @@ -678,7 +703,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip", ], - build_file = str(Label("//third_party:tflite_mobilenet.BUILD")), + build_file = clean_dep("//third_party:tflite_mobilenet.BUILD"), ) tf_http_archive( @@ -688,7 +713,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): "https://mirror.bazel.build/storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip", "https://storage.googleapis.com/download.tensorflow.org/models/tflite/smartreply_1.0_2017_11_01.zip" ], - build_file = str(Label("//third_party:tflite_smartreply.BUILD")), + build_file = clean_dep("//third_party:tflite_smartreply.BUILD"), ) ############################################################################## @@ -752,7 +777,7 @@ def tf_workspace(path_prefix="", tf_repo_name=""): # Needed by Protobuf native.bind( name = "python_headers", - actual = str(Label("//util/python:python_headers")), + actual = clean_dep("//util/python:python_headers"), ) # Needed by Protobuf diff --git a/third_party/mkl/BUILD b/third_party/mkl/BUILD index b27d341404..c2adf578c7 100644 --- a/third_party/mkl/BUILD +++ b/third_party/mkl/BUILD @@ -1,7 +1,5 @@ licenses(["notice"]) # 3-Clause BSD -exports_files(["LICENSE"]) - config_setting( name = "using_mkl", values = { @@ -10,17 +8,51 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "using_mkl_lnx_x64", + values = { + "cpu": "k8", + "define": "using_mkl=true", + }, + visibility = ["//visibility:public"], +) + load( "//third_party/mkl:build_defs.bzl", "if_mkl", ) +filegroup( + name = "LICENSE", + srcs = ["MKL_LICENSE"] + select({ + "@org_tensorflow//tensorflow:linux_x86_64": [ + "@mkl_linux//:LICENSE", + ], + "@org_tensorflow//tensorflow:darwin": [ + "@mkl_darwin//:LICENSE", + ], + "@org_tensorflow//tensorflow:windows": [ + "@mkl_windows//:LICENSE", + ], + }), + visibility = ["//visibility:public"], +) + cc_library( name = "intel_binary_blob", - srcs = if_mkl([ - "@mkl//:libmklml_intel.so", - "@mkl//:libiomp5.so", - ]), visibility = ["//visibility:public"], - deps = ["@mkl//:mkl_headers"], + deps = select({ + "@org_tensorflow//tensorflow:linux_x86_64": [ + "@mkl_linux//:mkl_headers", + "@mkl_linux//:mkl_libs_linux", + ], + "@org_tensorflow//tensorflow:darwin": [ + "@mkl_darwin//:mkl_headers", + "@mkl_darwin//:mkl_libs_darwin", + ], + "@org_tensorflow//tensorflow:windows": [ + "@mkl_windows//:mkl_headers", + "@mkl_windows//:mkl_libs_windows", + ], + }), ) diff --git a/third_party/mkl/MKL_LICENSE b/third_party/mkl/MKL_LICENSE new file mode 100644 index 0000000000..9c8f3ea087 --- /dev/null +++ b/third_party/mkl/MKL_LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/third_party/mkl/build_defs.bzl b/third_party/mkl/build_defs.bzl index 8b73ddabdd..53e02769da 100644 --- a/third_party/mkl/build_defs.bzl +++ b/third_party/mkl/build_defs.bzl @@ -24,6 +24,18 @@ def if_mkl(if_true, if_false = []): "//conditions:default": if_false }) +def if_mkl_lnx_x64(if_true, if_false = []): + """Shorthand for select()'ing on whether we're building with MKL. + + Returns a select statement which evaluates to if_true if we're building + with MKL enabled. Otherwise, the select statement evaluates to if_false. + + """ + return select({ + str(Label("//third_party/mkl:using_mkl_lnx_x64")): if_true, + "//conditions:default": if_false + }) + def _enable_local_mkl(repository_ctx): return _TF_MKL_ROOT in repository_ctx.os.environ diff --git a/third_party/mkl/mkl.BUILD b/third_party/mkl/mkl.BUILD index 8db97232e1..c3a71e4ff9 100644 --- a/third_party/mkl/mkl.BUILD +++ b/third_party/mkl/mkl.BUILD @@ -17,14 +17,29 @@ cc_library( visibility = ["//visibility:public"], ) -filegroup( - name = "libmklml_intel.so", - srcs = ["lib/libmklml_intel.so"], +cc_library( + name = "mkl_libs_linux", + srcs = [ + "lib/libiomp5.so", + "lib/libmklml_intel.so", + ], visibility = ["//visibility:public"], ) -filegroup( - name = "libiomp5.so", - srcs = ["lib/libiomp5.so"], +cc_library( + name = "mkl_libs_darwin", + srcs = [ + "lib/libiomp5.dylib", + "lib/libmklml.dylib", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "mkl_libs_windows", + srcs = [ + "lib/libiomp5md.lib", + "lib/mklml.lib", + ], visibility = ["//visibility:public"], ) -- GitLab From c194a0ea67a6fb61bd23b39fbb2a49b664e2dba1 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 11:02:56 -0700 Subject: [PATCH 417/906] Automated g4 rollback of changelist 190808678 PiperOrigin-RevId: 190955400 --- tensorflow/contrib/lite/toco/BUILD | 1 - .../graph_transformations.h | 1 - .../swap_elementwise_binary.cc | 175 ------------------ .../toco/graph_transformations/tests/BUILD | 11 -- .../tests/swap_elementwise_binary_test.cc | 89 --------- tensorflow/contrib/lite/toco/toco_tooling.cc | 1 - 6 files changed, 278 deletions(-) delete mode 100644 tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc delete mode 100644 tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc diff --git a/tensorflow/contrib/lite/toco/BUILD b/tensorflow/contrib/lite/toco/BUILD index bba61627f9..d552de313c 100644 --- a/tensorflow/contrib/lite/toco/BUILD +++ b/tensorflow/contrib/lite/toco/BUILD @@ -280,7 +280,6 @@ cc_library( "graph_transformations/resolve_tensorflow_switch.cc", "graph_transformations/resolve_tensorflow_tile.cc", "graph_transformations/resolve_transpose_attributes.cc", - "graph_transformations/swap_elementwise_binary.cc", "graph_transformations/unfuse_activation_functions.cc", "graph_transformations/unpartition_embedding_lookup.cc", "graph_transformations/unroll_batch_matmul.cc", diff --git a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h index 1291825c8e..640afc7c74 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h +++ b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h @@ -180,7 +180,6 @@ DECLARE_GRAPH_TRANSFORMATION(ResolveConstantStridedSlice) DECLARE_GRAPH_TRANSFORMATION(ResolveConstantFill) DECLARE_GRAPH_TRANSFORMATION(ResolveConstantGather) DECLARE_GRAPH_TRANSFORMATION(ResolveMultiplyByZero) -DECLARE_GRAPH_TRANSFORMATION(SwapElementwiseBinary) DECLARE_GRAPH_TRANSFORMATION(Dequantize) DECLARE_GRAPH_TRANSFORMATION(UnpartitionEmbeddingLookup) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc b/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc deleted file mode 100644 index ecbce58d16..0000000000 --- a/tensorflow/contrib/lite/toco/graph_transformations/swap_elementwise_binary.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include -#include -#include -#include - -#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" -#include "tensorflow/contrib/lite/toco/model.h" -#include "tensorflow/contrib/lite/toco/runtime/types.h" -#include "tensorflow/contrib/lite/toco/tooling_util.h" -#include "tensorflow/core/platform/logging.h" - -namespace toco { - -namespace { - -bool ShapesAllowSwapping(const string& input_array_name, - const string& const_array_name, Model* model) { - const Array& input_array = model->GetOrCreateArray(input_array_name); - const Array& const_array = model->GetOrCreateArray(const_array_name); - // Wait until these shapes have been resolved. - if (!input_array.has_shape() || !const_array.has_shape()) { - return false; - } - - // Currently swapping is not handled for scalar const_array, though that could - // be done once there is a test model. - if (RequiredBufferSizeForShape(input_array.shape()) != - RequiredBufferSizeForShape(const_array.shape())) { - return false; - } - - return true; -} - -} // namespace - -// Swaps: -// Input -// \ -// (Reshape Op) Const -// \ / -// (Add/Sub/Mul/Div op) -// | -// Output -// -// To: -// -// Input Const -// \ / -// (Add/Sub/Mul/Div op) -// | -// (Reshape Op) -// | -// Output -// -// This can allow Add/Mul ops from batch normalization to be folded into an -// Input op from a FullyConnected layer. -bool SwapElementwiseBinary::Run(Model* model, std::size_t op_index) { - const auto element_wise_op_it = model->operators.begin() + op_index; - std::unique_ptr& element_wise_op = *element_wise_op_it; - DCHECK(element_wise_op); - - switch (element_wise_op->type) { - case OperatorType::kAdd: - case OperatorType::kSub: - case OperatorType::kMul: - case OperatorType::kDiv: - break; - default: - return false; - } - - int reshape_input = -1; - Operator* op = GetOpWithOutput(*model, element_wise_op->inputs[0]); - if (!op) { - return false; - } - - if (op->type == OperatorType::kTensorFlowReshape) { - reshape_input = 0; - } else { - op = GetOpWithOutput(*model, element_wise_op->inputs[1]); - if (!op || op->type != OperatorType::kTensorFlowReshape) { - return false; - } - reshape_input = 1; - } - - int const_input = (reshape_input == 0) ? 1 : 0; - const string& const_input_array = element_wise_op->inputs[const_input]; - if (!IsConstantParameterArray(*model, const_input_array)) { - return false; - } - - // Do not fold division if denominator is not constant. - if (element_wise_op->type != OperatorType::kDiv && const_input != 1) { - return false; - } - - const auto reshape_it = - FindOpWithOutput(*model, element_wise_op->inputs[reshape_input]); - // Note: we take copies of the tensor names here, instead of const-refs as we - // may overwrite the original names. - const string reshape_input_name = (*reshape_it)->inputs[0]; - const string intermediate_name = (*reshape_it)->outputs[0]; - const string element_wise_output_name = element_wise_op->outputs[0]; - - // Check the reshape op input and const op have their shapes resolved. - if (!ShapesAllowSwapping(reshape_input_name, const_input_array, model)) { - return false; - } - - int count_ops_consuming_output = CountOpsWithInput(*model, intermediate_name); - DCHECK_GE(count_ops_consuming_output, 1); - if (count_ops_consuming_output > 1) { - AddMessageF( - "Not exchanging element-wise function with %s because it is " - "consumed by more than 1 other operator", - LogName(**reshape_it)); - return false; - } - - // If the element_wise_op was originally producing an output_array we can't - // swap as otherwise the output array would change. It'd be nice to still be - // able to swap but if code is relying on the fetch names instead of array - // indices this won't work. - for (int i = 0; i < model->flags.output_arrays_size(); ++i) { - if (model->flags.output_arrays(i) == element_wise_op->outputs[0]) { - AddMessageF( - "Not exchanging activation function with %s to preserve output array " - "name %s", - LogName(**reshape_it), element_wise_op->outputs[0]); - return false; - } - } - - // Rewire by changing inputs, including all consumers. - // TODO(b/76086261): Replace with new utility function. - Operator* consumer = GetFirstOpWithInput(*model, element_wise_output_name); - while (consumer) { - for (int i = 0; i < consumer->inputs.size(); ++i) { - if (consumer->inputs[i] == element_wise_output_name) { - consumer->inputs[i] = intermediate_name; - } - } - consumer = GetFirstOpWithInput(*model, element_wise_output_name); - } - element_wise_op->inputs[reshape_input] = reshape_input_name; - (*reshape_it)->inputs[0] = element_wise_output_name; - - // Clear shapes; this will allow shape propagation to fix the sizes for us. - model->GetOrCreateArray(element_wise_output_name).clear_shape(); - - // Finally, swap operators. Note that this only works when there are no other - // direct descendents of the reshape operator. - element_wise_op.swap(*reshape_it); - - return true; -} - -} // namespace toco diff --git a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD index a2008ddbdb..8dcd4adc90 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD +++ b/tensorflow/contrib/lite/toco/graph_transformations/tests/BUILD @@ -18,17 +18,6 @@ tf_cc_test( ], ) -tf_cc_test( - name = "swap_elementwise_binary_test", - srcs = ["swap_elementwise_binary_test.cc"], - deps = [ - "//tensorflow/contrib/lite/toco:graph_transformations", - "//tensorflow/contrib/lite/toco:model", - "//tensorflow/contrib/lite/toco:tooling_util", - "@com_google_googletest//:gtest_main", - ], -) - tf_cc_test( name = "lstm_utils_test", srcs = ["lstm_utils_test.cc"], diff --git a/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc b/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc deleted file mode 100644 index c3778017f3..0000000000 --- a/tensorflow/contrib/lite/toco/graph_transformations/tests/swap_elementwise_binary_test.cc +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ -#include -#include -#include -#include - -#include -#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" -#include "tensorflow/contrib/lite/toco/model.h" -#include "tensorflow/contrib/lite/toco/tooling_util.h" - -namespace toco { - -namespace { - -int ShapeCount(const std::vector& size) { - CHECK(size.size()); - int count = 1; - for (int dim : size) { - count *= dim; - } - return count; -} - -// Adds a new parameter array to the model. -void AddConstArray(const string& name, const float* data, - const std::vector& size, Model* model) { - Array& array = model->GetOrCreateArray(name); - array.data_type = ArrayDataType::kFloat; - Shape* shape = array.mutable_shape(); - *(shape->mutable_dims()) = size; - - auto& buffer = array.GetMutableBuffer(); - buffer.data.resize(ShapeCount(size)); - std::copy(data, data + ShapeCount(size), buffer.data.data()); -} - -} // namespace - -TEST(SwapElementwiseBinaryTest, SwapsReshape) { - Model model; - const float parameters[2][4] = {{0., 1., 2., 3.}, {10., 11., 12., 13.}}; - - AddConstArray("before_reshape", parameters[0], {2, 2}, &model); - AddConstArray("add_vector", parameters[1], {1, 4}, &model); - - auto reshape_op = absl::make_unique(); - reshape_op->shape = {1, 4}; - reshape_op->inputs = {"before_reshape"}; - reshape_op->outputs = {"after_reshape"}; - Array& reshape_array = model.GetOrCreateArray("after_reshape"); - *(reshape_array.mutable_shape()) = {1, 4}; - - auto add_op = absl::make_unique(); - add_op->inputs = {"after_reshape", "add_vector"}; - add_op->outputs = {"add"}; - Array& add_array = model.GetOrCreateArray("add"); - *(add_array.mutable_shape()) = {1, 4}; - - model.operators.push_back(std::move(reshape_op)); - model.operators.push_back(std::move(add_op)); - - auto transformation = absl::make_unique(); - ASSERT_TRUE(transformation->Run(&model, 1)); - - Operator* op = GetOpWithOutput(model, "add"); - ASSERT_NE(nullptr, op); - ASSERT_EQ(OperatorType::kAdd, op->type); - ASSERT_EQ(2, op->inputs.size()); - for (const string& input : op->inputs) { - EXPECT_TRUE(IsConstantParameterArray(model, input)) - << input << " is not const input"; - } -} - -} // namespace toco diff --git a/tensorflow/contrib/lite/toco/toco_tooling.cc b/tensorflow/contrib/lite/toco/toco_tooling.cc index 41ea1481bc..30dd6fab9e 100644 --- a/tensorflow/contrib/lite/toco/toco_tooling.cc +++ b/tensorflow/contrib/lite/toco/toco_tooling.cc @@ -90,7 +90,6 @@ void MakeGeneralGraphTransformationsSet( transformations->Add(new ResolveTensorFlowTile); transformations->Add(new ResolveTensorFlowConcat); transformations->Add(new ResolveMultiplyByZero); - transformations->Add(new SwapElementwiseBinary); transformations->Add(new IdentifyDilatedConv); transformations->Add(new IdentifyL2Normalization); transformations->Add(new IdentifyL2Pool); -- GitLab From 1d7c2fa60f717dea7239970d96f7d4bf96842039 Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Fri, 30 Mar 2018 02:11:55 +0800 Subject: [PATCH 418/906] Raise a nicer error message when trying to call gradients with while loop (#18052) * Produce a nicer error message when trying to call gradients on a while loop without properly serializing graph via MetaGraphDef * Fix syntax and lint error * Fix minor intent: Wrong continued indentation (add 2 spaces) --- tensorflow/python/ops/control_flow_ops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/python/ops/control_flow_ops.py b/tensorflow/python/ops/control_flow_ops.py index 1278768d8b..710287012e 100644 --- a/tensorflow/python/ops/control_flow_ops.py +++ b/tensorflow/python/ops/control_flow_ops.py @@ -833,6 +833,9 @@ class GradLoopState(object): if outer_grad_state: outer_forward_ctxt = outer_grad_state.forward_context else: + if not hasattr(forward_ctxt, 'outer_context'): + raise ValueError("Failed to call gradients on a while loop without" + "properly serializing graph via MetaGraphDef") outer_forward_ctxt = forward_ctxt.outer_context # Add the forward loop counter. -- GitLab From d044a1ffa87e772076a14ace7a16bb97886a0804 Mon Sep 17 00:00:00 2001 From: Alan Yee Date: Thu, 29 Mar 2018 11:12:07 -0700 Subject: [PATCH 419/906] Update README.md (#18076) Add YouTube channel --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0a309ebe2d..c66f7e3f3f 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ The TensorFlow project strives to abide by generally accepted best practices in * [TensorFlow Website](https://www.tensorflow.org) * [TensorFlow White Papers](https://www.tensorflow.org/about/bib) +* [TensorFlow YouTube Channel](https://www.youtube.com/channel/UC0rqucBdTuFTjJiefW5t-IQ) * [TensorFlow Model Zoo](https://github.com/tensorflow/models) * [TensorFlow MOOC on Udacity](https://www.udacity.com/course/deep-learning--ud730) * [TensorFlow Course at Stanford](https://web.stanford.edu/class/cs20si) -- GitLab From f2c3b869d354c05e497a79118a13a599dfc256bc Mon Sep 17 00:00:00 2001 From: "Xiaoming (Jason) Cui" Date: Thu, 29 Mar 2018 11:12:36 -0700 Subject: [PATCH 420/906] [INTEL MKL] utilize test_util.IsMklEnabled() to check if the MKL support is turned on or not (#18062) * Fixed issue #92, timeline_test unit test fails, changed the test so that it can take cpu name changed with MKLDNN naming conversion * [INTEL MKL] utilize test_util.IsMklEnabled() to check if the MKL support is turned on or not --- tensorflow/python/client/timeline_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/client/timeline_test.py b/tensorflow/python/client/timeline_test.py index 5e6b5acdb0..c046e9cfd4 100644 --- a/tensorflow/python/client/timeline_test.py +++ b/tensorflow/python/client/timeline_test.py @@ -24,6 +24,7 @@ from tensorflow.core.protobuf import config_pb2 from tensorflow.python.client import session from tensorflow.python.client import timeline from tensorflow.python.framework import constant_op +from tensorflow.python.framework import test_util from tensorflow.python.framework import ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import variables @@ -155,9 +156,7 @@ class TimelineTest(test.TestCase): ctf = step_analysis.chrome_trace.format_to_string() self._validateTrace(ctf) maximums = step_analysis.allocator_maximums - cpuname = 'cpu' - if 'mklcpu' in maximums: - cpuname = 'mkl' + cpuname + cpuname = 'mklcpu' if test_util.IsMklEnabled() else 'cpu' self.assertTrue(cpuname in maximums) cpu_max = maximums[ 'cuda_host_bfc'] if 'cuda_host_bfc' in maximums else maximums[cpuname] -- GitLab From 7c7350dfb35276eff2b8039bfa2def13bb736a4b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 11:24:44 -0700 Subject: [PATCH 421/906] Update ops-related pbtxt files. PiperOrigin-RevId: 190959179 --- .../core/ops/compat/ops_history.v1.pbtxt | 348 ++++++++++++++++++ tensorflow/core/ops/ops.pbtxt | 348 ++++++++++++++++++ 2 files changed, 696 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 05d6e02281..7cdf36f423 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -10340,6 +10340,342 @@ op { } is_commutative: true } +op { + name: "BoostedTreesCalculateBestGainsPerFeature" + input_arg { + name: "node_id_range" + type: DT_INT32 + } + input_arg { + name: "stats_summary_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "node_ids_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "gains_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "thresholds_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "left_node_contribs_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "right_node_contribs_list" + type: DT_FLOAT + number_attr: "num_features" + } + attr { + name: "l1" + type: "float" + } + attr { + name: "l2" + type: "float" + } + attr { + name: "tree_complexity" + type: "float" + } + attr { + name: "max_splits" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_features" + type: "int" + has_minimum: true + minimum: 1 + } +} +op { + name: "BoostedTreesCreateEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "stamp_token" + type: DT_INT64 + } + input_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesDeserializeEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "stamp_token" + type: DT_INT64 + } + input_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesEnsembleResourceHandleOp" + output_arg { + name: "resource" + type: DT_RESOURCE + } + attr { + name: "container" + type: "string" + default_value { + s: "" + } + } + attr { + name: "shared_name" + type: "string" + default_value { + s: "" + } + } + is_stateful: true +} +op { + name: "BoostedTreesGetEnsembleStates" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "stamp_token" + type: DT_INT64 + } + output_arg { + name: "num_trees" + type: DT_INT32 + } + output_arg { + name: "num_finalized_trees" + type: DT_INT32 + } + output_arg { + name: "num_attempted_layers" + type: DT_INT32 + } + is_stateful: true +} +op { + name: "BoostedTreesMakeStatsSummary" + input_arg { + name: "node_ids" + type: DT_INT32 + } + input_arg { + name: "gradients" + type: DT_FLOAT + } + input_arg { + name: "hessians" + type: DT_FLOAT + } + input_arg { + name: "bucketized_features_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "stats_summary" + type: DT_FLOAT + } + attr { + name: "max_splits" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_buckets" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_features" + type: "int" + has_minimum: true + minimum: 1 + } +} +op { + name: "BoostedTreesPredict" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "bucketized_features" + type: DT_INT32 + number_attr: "num_bucketized_features" + } + output_arg { + name: "logits" + type: DT_FLOAT + } + attr { + name: "num_bucketized_features" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "logits_dimension" + type: "int" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + is_stateful: true +} +op { + name: "BoostedTreesSerializeEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "stamp_token" + type: DT_INT64 + } + output_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesTrainingPredict" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "cached_tree_ids" + type: DT_INT32 + } + input_arg { + name: "cached_node_ids" + type: DT_INT32 + } + input_arg { + name: "bucketized_features" + type: DT_INT32 + number_attr: "num_bucketized_features" + } + output_arg { + name: "partial_logits" + type: DT_FLOAT + } + output_arg { + name: "tree_ids" + type: DT_INT32 + } + output_arg { + name: "node_ids" + type: DT_INT32 + } + attr { + name: "num_bucketized_features" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "logits_dimension" + type: "int" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + is_stateful: true +} +op { + name: "BoostedTreesUpdateEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "feature_ids" + type: DT_INT32 + } + input_arg { + name: "node_ids" + type: DT_INT32 + number_attr: "num_features" + } + input_arg { + name: "gains" + type: DT_FLOAT + number_attr: "num_features" + } + input_arg { + name: "thresholds" + type: DT_INT32 + number_attr: "num_features" + } + input_arg { + name: "left_node_contribs" + type: DT_FLOAT + number_attr: "num_features" + } + input_arg { + name: "right_node_contribs" + type: DT_FLOAT + number_attr: "num_features" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "learning_rate" + type: "float" + } + attr { + name: "pruning_mode" + type: "int" + has_minimum: true + } + attr { + name: "num_features" + type: "int" + has_minimum: true + } + is_stateful: true +} op { name: "BroadcastArgs" input_arg { @@ -23333,6 +23669,18 @@ op { } } } +op { + name: "IsBoostedTreesEnsembleInitialized" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "is_initialized" + type: DT_BOOL + } + is_stateful: true +} op { name: "IsFinite" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index 274a7fbf75..42a68cb712 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -3995,6 +3995,342 @@ op { } is_commutative: true } +op { + name: "BoostedTreesCalculateBestGainsPerFeature" + input_arg { + name: "node_id_range" + type: DT_INT32 + } + input_arg { + name: "stats_summary_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "node_ids_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "gains_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "thresholds_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "left_node_contribs_list" + type: DT_FLOAT + number_attr: "num_features" + } + output_arg { + name: "right_node_contribs_list" + type: DT_FLOAT + number_attr: "num_features" + } + attr { + name: "l1" + type: "float" + } + attr { + name: "l2" + type: "float" + } + attr { + name: "tree_complexity" + type: "float" + } + attr { + name: "max_splits" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_features" + type: "int" + has_minimum: true + minimum: 1 + } +} +op { + name: "BoostedTreesCreateEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "stamp_token" + type: DT_INT64 + } + input_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesDeserializeEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "stamp_token" + type: DT_INT64 + } + input_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesEnsembleResourceHandleOp" + output_arg { + name: "resource" + type: DT_RESOURCE + } + attr { + name: "container" + type: "string" + default_value { + s: "" + } + } + attr { + name: "shared_name" + type: "string" + default_value { + s: "" + } + } + is_stateful: true +} +op { + name: "BoostedTreesGetEnsembleStates" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "stamp_token" + type: DT_INT64 + } + output_arg { + name: "num_trees" + type: DT_INT32 + } + output_arg { + name: "num_finalized_trees" + type: DT_INT32 + } + output_arg { + name: "num_attempted_layers" + type: DT_INT32 + } + is_stateful: true +} +op { + name: "BoostedTreesMakeStatsSummary" + input_arg { + name: "node_ids" + type: DT_INT32 + } + input_arg { + name: "gradients" + type: DT_FLOAT + } + input_arg { + name: "hessians" + type: DT_FLOAT + } + input_arg { + name: "bucketized_features_list" + type: DT_INT32 + number_attr: "num_features" + } + output_arg { + name: "stats_summary" + type: DT_FLOAT + } + attr { + name: "max_splits" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_buckets" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "num_features" + type: "int" + has_minimum: true + minimum: 1 + } +} +op { + name: "BoostedTreesPredict" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "bucketized_features" + type: DT_INT32 + number_attr: "num_bucketized_features" + } + output_arg { + name: "logits" + type: DT_FLOAT + } + attr { + name: "num_bucketized_features" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "logits_dimension" + type: "int" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + is_stateful: true +} +op { + name: "BoostedTreesSerializeEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "stamp_token" + type: DT_INT64 + } + output_arg { + name: "tree_ensemble_serialized" + type: DT_STRING + } + is_stateful: true +} +op { + name: "BoostedTreesTrainingPredict" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "cached_tree_ids" + type: DT_INT32 + } + input_arg { + name: "cached_node_ids" + type: DT_INT32 + } + input_arg { + name: "bucketized_features" + type: DT_INT32 + number_attr: "num_bucketized_features" + } + output_arg { + name: "partial_logits" + type: DT_FLOAT + } + output_arg { + name: "tree_ids" + type: DT_INT32 + } + output_arg { + name: "node_ids" + type: DT_INT32 + } + attr { + name: "num_bucketized_features" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "logits_dimension" + type: "int" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + is_stateful: true +} +op { + name: "BoostedTreesUpdateEnsemble" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + input_arg { + name: "feature_ids" + type: DT_INT32 + } + input_arg { + name: "node_ids" + type: DT_INT32 + number_attr: "num_features" + } + input_arg { + name: "gains" + type: DT_FLOAT + number_attr: "num_features" + } + input_arg { + name: "thresholds" + type: DT_INT32 + number_attr: "num_features" + } + input_arg { + name: "left_node_contribs" + type: DT_FLOAT + number_attr: "num_features" + } + input_arg { + name: "right_node_contribs" + type: DT_FLOAT + number_attr: "num_features" + } + attr { + name: "max_depth" + type: "int" + has_minimum: true + minimum: 1 + } + attr { + name: "learning_rate" + type: "float" + } + attr { + name: "pruning_mode" + type: "int" + has_minimum: true + } + attr { + name: "num_features" + type: "int" + has_minimum: true + } + is_stateful: true +} op { name: "BroadcastArgs" input_arg { @@ -11365,6 +11701,18 @@ op { } } } +op { + name: "IsBoostedTreesEnsembleInitialized" + input_arg { + name: "tree_ensemble_handle" + type: DT_RESOURCE + } + output_arg { + name: "is_initialized" + type: DT_BOOL + } + is_stateful: true +} op { name: "IsFinite" input_arg { -- GitLab From 1c6e292e7cc348218db2048b241a7330cacbbef6 Mon Sep 17 00:00:00 2001 From: Ayush Dubey Date: Thu, 29 Mar 2018 11:54:55 -0700 Subject: [PATCH 422/906] Initialize pointer to ScopedAllocatorMgr in BaseGPUDevice. PiperOrigin-RevId: 190964008 --- tensorflow/core/common_runtime/gpu/gpu_device.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/core/common_runtime/gpu/gpu_device.cc b/tensorflow/core/common_runtime/gpu/gpu_device.cc index 52fd20e479..0b9e8f9cc2 100644 --- a/tensorflow/core/common_runtime/gpu/gpu_device.cc +++ b/tensorflow/core/common_runtime/gpu/gpu_device.cc @@ -257,6 +257,7 @@ BaseGPUDevice::BaseGPUDevice(const SessionOptions& options, const string& name, physical_device_desc)), gpu_allocator_(gpu_allocator), cpu_allocator_(cpu_allocator), + scoped_allocator_mgr_(new ScopedAllocatorMgr(name)), tf_gpu_id_(tf_gpu_id), sync_every_op_(sync_every_op), max_streams_(max_streams) { -- GitLab From 4ebb2eac303a22b06597facd07793595e105169b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 12:02:50 -0700 Subject: [PATCH 423/906] Leaves attributes on outside_compilation nodes so they can be replicated in a later pass. PiperOrigin-RevId: 190965218 --- .../jit/encapsulate_subgraphs_pass.cc | 51 ++-- .../jit/encapsulate_subgraphs_pass_test.cc | 279 ++++++++++-------- tensorflow/contrib/tpu/ops/replication_ops.cc | 2 + 3 files changed, 187 insertions(+), 145 deletions(-) diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc index 7fc43fb263..53ec6c1e60 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.cc @@ -254,7 +254,8 @@ class Encapsulator { // Adds _RecvAtHost and _SendFromHost nodes, where needed, to graph_out. Status AddOutsideCompilationHostIONodes( - const string& subgraph_name, + const string& group_attribute, const string& subgraph_name, + const string& outside_compilation_attribute, const std::unordered_map& node_images, Graph* graph_out); @@ -405,7 +406,9 @@ class Encapsulator { // Builds a _RecvAtHost node producing all the inputs of an // outside_compilation subgraph and stores it in oc_subgraph.recv_at_host. - Status AddRecvAtHostNode(const string& subgraph_name, + Status AddRecvAtHostNode(const string& group_attribute, + const string& subgraph_name, + const string& outside_compilation_attribute, const string& oc_subgraph_name, OutsideCompilationSubgraph* oc_subgraph, Graph* graph_out); @@ -414,8 +417,10 @@ class Encapsulator { // outside_compilation subgraph and stores it in oc_subgraph.send_from_host. Status AddSendFromHostNode( const std::unordered_map& node_images, - const string& subgraph_name, const string& oc_subgraph_name, - OutsideCompilationSubgraph* oc_subgraph, Graph* graph_out); + const string& group_attribute, const string& subgraph_name, + const string& outside_compilation_attribute, + const string& oc_subgraph_name, OutsideCompilationSubgraph* oc_subgraph, + Graph* graph_out); // The subgraph extracted from the input graph, suitable for being turned // into a FunctionDef. Inputs are fed by _Arg nodes, and outputs are @@ -1114,7 +1119,8 @@ Status Encapsulator::Subgraph::AddHostComputeKeyPlaceholder( } Status Encapsulator::Subgraph::AddRecvAtHostNode( - const string& subgraph_name, const string& oc_subgraph_name, + const string& group_attribute, const string& subgraph_name, + const string& outside_compilation_attribute, const string& oc_subgraph_name, OutsideCompilationSubgraph* oc_subgraph, Graph* graph_out) { if (host_compute_key_placeholder_ == nullptr) { TF_RETURN_IF_ERROR(AddHostComputeKeyPlaceholder(oc_subgraph, graph_out)); @@ -1135,14 +1141,15 @@ Status Encapsulator::Subgraph::AddRecvAtHostNode( NodeDefBuilder builder(strings::StrCat("outside_compilation_", subgraph_name, "_", oc_subgraph_name, "_recv"), kRecvAtHostOp); - // TODO(misard) When we add replication the device placement will have to be - // redone. builder.Device(device_); builder.Attr("Toutputs", dtypes); - // TODO(misard) For now we only support TPU device 0. + // The correct device_ordinal will be inserted during replication in a + // subsequent rewrite. builder.Attr("device_ordinal", 0); builder.Attr("key", strings::StrCat("host_compute_channel_", subgraph_name, "_", oc_subgraph_name)); + builder.Attr(group_attribute, subgraph_name); + builder.Attr(outside_compilation_attribute, oc_subgraph_name); builder.Input(host_compute_key_placeholder_->name(), 0, DT_STRING); Status s = builder.Finalize(&recv_def); if (!s.ok()) return s; @@ -1163,7 +1170,8 @@ Status Encapsulator::Subgraph::AddRecvAtHostNode( Status Encapsulator::Subgraph::AddSendFromHostNode( const std::unordered_map& node_images, - const string& subgraph_name, const string& oc_subgraph_name, + const string& group_attribute, const string& subgraph_name, + const string& outside_compilation_attribute, const string& oc_subgraph_name, OutsideCompilationSubgraph* oc_subgraph, Graph* graph_out) { if (host_compute_key_placeholder_ == nullptr) { TF_RETURN_IF_ERROR(AddHostComputeKeyPlaceholder(oc_subgraph, graph_out)); @@ -1188,14 +1196,15 @@ Status Encapsulator::Subgraph::AddSendFromHostNode( NodeDefBuilder builder(strings::StrCat("outside_compilation_", subgraph_name, "_", oc_subgraph_name, "_send"), kSendFromHostOp); - // TODO(misard) When we add replication the device placement will have to be - // redone. builder.Device(device_); builder.Attr("Tinputs", dtypes); builder.Attr("key", strings::StrCat("host_compute_channel_", subgraph_name, "_", oc_subgraph_name)); - // TODO(misard) For now we only support TPU device 0. + // The correct device_ordinal will be inserted during replication in a + // subsequent rewrite. builder.Attr("device_ordinal", 0); + builder.Attr(group_attribute, subgraph_name); + builder.Attr(outside_compilation_attribute, oc_subgraph_name); builder.Input(inputs); builder.Input(host_compute_key_placeholder_->name(), 0, DT_STRING); Status s = builder.Finalize(&send_def); @@ -1216,7 +1225,8 @@ Status Encapsulator::Subgraph::AddSendFromHostNode( } Status Encapsulator::Subgraph::AddOutsideCompilationHostIONodes( - const string& subgraph_name, + const string& group_attribute, const string& subgraph_name, + const string& outside_compilation_attribute, const std::unordered_map& node_images, Graph* graph_out) { for (auto& outside_compilation_subgraph_entry : @@ -1226,14 +1236,16 @@ Status Encapsulator::Subgraph::AddOutsideCompilationHostIONodes( outside_compilation_subgraph_entry.second; if (!oc_subgraph.inputs.empty() || !oc_subgraph.control_inputs.empty()) { - TF_RETURN_IF_ERROR( - AddRecvAtHostNode(subgraph_name, oc_name, &oc_subgraph, graph_out)); + TF_RETURN_IF_ERROR(AddRecvAtHostNode(group_attribute, subgraph_name, + outside_compilation_attribute, + oc_name, &oc_subgraph, graph_out)); } if (!oc_subgraph.outputs_by_src.empty() || !oc_subgraph.control_outputs.empty()) { - TF_RETURN_IF_ERROR(AddSendFromHostNode(node_images, subgraph_name, - oc_name, &oc_subgraph, graph_out)); + TF_RETURN_IF_ERROR(AddSendFromHostNode( + node_images, group_attribute, subgraph_name, + outside_compilation_attribute, oc_name, &oc_subgraph, graph_out)); } } return Status::OK(); @@ -1450,8 +1462,6 @@ Status Encapsulator::CopyNodesToOutputGraph( "Parallel checking is not supported when outside_compilation " "clusters are present."); } - image->ClearAttr(group_attribute_); - image->ClearAttr(outside_compilation_attribute_); } (*node_images)[node] = image; } @@ -1477,7 +1487,8 @@ Status Encapsulator::AddOutsideCompilationHostIONodes( const string& subgraph_name = subgraph_entry.first; Subgraph& subgraph = subgraph_entry.second; TF_RETURN_IF_ERROR(subgraph.AddOutsideCompilationHostIONodes( - subgraph_name, node_images, graph_out)); + group_attribute_, subgraph_name, outside_compilation_attribute_, + node_images, graph_out)); } return Status::OK(); } diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc index 94481a1fde..7899b5d72d 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass_test.cc @@ -382,24 +382,36 @@ Node* KeyPlaceholder(const string& call_node, .FinalizeBuilder(&node_builder); } -Node* RecvAtHost(ops::NodeOut key_input, const string& key, +Node* RecvAtHost(ops::NodeOut key_input, const string& cluster, + const string& oc_cluster, const gtl::ArraySlice& dtypes, const GraphDefBuilder::Options& opts) { if (opts.HaveError()) return nullptr; - NodeBuilder node_builder(opts.GetNameForOp("_XlaRecvAtHost"), + string key = + strings::StrCat("host_compute_channel_", cluster, "_", oc_cluster); + string name = strings::StrCat("outside_compilation_", cluster, "_", + oc_cluster, "_recv"); + NodeBuilder node_builder(opts.WithName(name).GetNameForOp("_XlaRecvAtHost"), "_XlaRecvAtHost", opts.op_registry()); node_builder.Input(std::move(key_input)); return opts.WithAttr("Toutputs", dtypes) .WithAttr("key", key) .WithAttr("device_ordinal", 0) + .WithAttr("_encapsulate", cluster) + .WithAttr("_outside", oc_cluster) .FinalizeBuilder(&node_builder); } -Node* SendFromHost(ops::NodeOut key_input, const string& key, +Node* SendFromHost(ops::NodeOut key_input, const string& cluster, + const string& oc_cluster, const std::vector& inputs, const GraphDefBuilder::Options& opts) { if (opts.HaveError()) return nullptr; - NodeBuilder node_builder(opts.GetNameForOp("_XlaSendFromHost"), + string key = + strings::StrCat("host_compute_channel_", cluster, "_", oc_cluster); + string name = strings::StrCat("outside_compilation_", cluster, "_", + oc_cluster, "_send"); + NodeBuilder node_builder(opts.WithName(name).GetNameForOp("_XlaSendFromHost"), "_XlaSendFromHost", opts.op_registry()); node_builder.Input(inputs); node_builder.Input(std::move(key_input)); @@ -410,6 +422,8 @@ Node* SendFromHost(ops::NodeOut key_input, const string& key, return opts.WithAttr("Tinputs", dtypes) .WithAttr("key", key) .WithAttr("device_ordinal", 0) + .WithAttr("_encapsulate", cluster) + .WithAttr("_outside", oc_cluster) .FinalizeBuilder(&node_builder); } @@ -856,14 +870,14 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { GraphDefBuilder shape(GraphDefBuilder::kFailImmediately); Node* key_constant = KeyPlaceholderShape(shape.opts().WithName("KnownShape/_0")); - Node* recv = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - shape.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, shape.opts()); Node* e = Binary(ops::NodeOut(recv, 0), ops::NodeOut(recv, 1), - shape.opts().WithName("E")); - SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); + shape.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, shape.opts()); TF_EXPECT_OK( AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } @@ -901,17 +915,16 @@ TEST(EncapsulateSubgraphsTest, OneFunctionOneOutside) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, b2.opts()); Node* e = Binary(ops::NodeOut(recv, 0), ops::NodeOut(recv, 1), - b2.opts().WithName("E").WithControlInputs({recv, b})); - Node* send = SendFromHost(ops::NodeOut(key_constant, 0), - "host_compute_channel_F1_O1", {e}, - b2.opts() - .WithName("outside_compilation_F1_O1_send") - .WithControlInput(e)); + b2.opts() + .WithName("E") + .WithControlInputs({recv, b}) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send = SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, + b2.opts().WithControlInput(e)); Node* s = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInputs({recv, send}), @@ -976,14 +989,14 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { GraphDefBuilder shape1(GraphDefBuilder::kFailImmediately); Node* key_constant = KeyPlaceholderShape(shape1.opts().WithName("KnownShape/_0")); - Node* recv = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - shape1.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, shape1.opts()); Node* e = Binary(ops::NodeOut(recv, 0), ops::NodeOut(recv, 1), - shape1.opts().WithName("E")); - SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {e}, shape1.opts().WithName("outside_compilation_F1_O1_send")); + shape1.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, shape1.opts()); TF_EXPECT_OK( AddGraphDefToFunctionLibrary(shape1, "F1_O1", &library_expected)); } @@ -992,19 +1005,21 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { GraphDefBuilder shape2(GraphDefBuilder::kFailImmediately); Node* key_constant = KeyPlaceholderShape(shape2.opts().WithName("KnownShape/_0")); - Node* recv1 = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - shape2.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv1 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, shape2.opts()); Node* e = Binary(ops::NodeOut(recv1, 0), ops::NodeOut(recv1, 1), - shape2.opts().WithName("E")); - Node* recv2 = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O2", - {DT_FLOAT, DT_FLOAT}, - shape2.opts().WithName("outside_compilation_F1_O2_recv")); - Node* h = Binary(ops::NodeOut(recv2, 0), e, shape2.opts().WithName("H")); - SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O2", - {h}, shape2.opts().WithName("outside_compilation_F1_O2_send")); + shape2.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* recv2 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O2", + {DT_FLOAT, DT_FLOAT}, shape2.opts()); + Node* h = Binary(ops::NodeOut(recv2, 0), e, + shape2.opts() + .WithName("H") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O2")); + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O2", {h}, shape2.opts()); TF_EXPECT_OK( AddGraphDefToFunctionLibrary(shape2, "F1_O2", &library_expected)); } @@ -1054,28 +1069,32 @@ TEST(EncapsulateSubgraphsTest, OneFunctionTwoOutside) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv1 = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv1 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, b2.opts()); Node* e = Binary(ops::NodeOut(recv1, 0), ops::NodeOut(recv1, 1), - b2.opts().WithName("E").WithControlInputs({recv1, b})); - Node* send1 = SendFromHost(ops::NodeOut(key_constant, 0), - "host_compute_channel_F1_O1", {e}, - b2.opts() - .WithName("outside_compilation_F1_O1_send") - .WithControlInput(e)); - - Node* recv2 = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O2", - {DT_FLOAT, DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O2_recv")); + b2.opts() + .WithName("E") + .WithControlInputs({recv1, b}) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send1 = SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, + b2.opts().WithControlInput(e)); + + Node* recv2 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O2", + {DT_FLOAT, DT_FLOAT}, b2.opts()); Node* g = Binary(e, ops::NodeOut(recv2, 1), - b2.opts().WithName("G").WithControlInputs({recv2, e})); - Node* h = Binary(ops::NodeOut(recv2, 0), e, b2.opts().WithName("H")); - Node* send2 = SendFromHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O2", {h}, - b2.opts().WithName("outside_compilation_F1_O2_send")); + b2.opts() + .WithName("G") + .WithControlInputs({recv2, e}) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O2")); + Node* h = Binary(ops::NodeOut(recv2, 0), e, + b2.opts() + .WithName("H") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O2")); + Node* send2 = + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O2", {h}, b2.opts()); Node* s = Sequencer(b2.opts() .WithName("F1_sequencer") @@ -1139,14 +1158,14 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { GraphDefBuilder shape(GraphDefBuilder::kFailImmediately); Node* key_constant = KeyPlaceholderShape(shape.opts().WithName("KnownShape/_0")); - Node* recv = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - shape.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, shape.opts()); Node* e = Binary(ops::NodeOut(recv, 0), ops::NodeOut(recv, 1), - shape.opts().WithName("E")); - SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); + shape.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, shape.opts()); TF_EXPECT_OK( AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } @@ -1207,17 +1226,16 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { Node* key_constant1 = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv1 = - RecvAtHost(ops::NodeOut(key_constant1, 0), "host_compute_channel_F1_O1", - {DT_FLOAT, DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); + Node* recv1 = RecvAtHost(ops::NodeOut(key_constant1, 0), "F1", "O1", + {DT_FLOAT, DT_FLOAT}, b2.opts()); Node* e = Binary(ops::NodeOut(recv1, 0), ops::NodeOut(recv1, 1), - b2.opts().WithName("E").WithControlInputs({recv1, b})); - Node* send1 = SendFromHost(ops::NodeOut(key_constant1, 0), - "host_compute_channel_F1_O1", {e}, - b2.opts() - .WithName("outside_compilation_F1_O1_send") - .WithControlInput(e)); + b2.opts() + .WithName("E") + .WithControlInputs({recv1, b}) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send1 = SendFromHost(ops::NodeOut(key_constant1, 0), "F1", "O1", {e}, + b2.opts().WithControlInput(e)); Node* s1 = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInputs({recv1, send1}), "F1"); @@ -1229,13 +1247,15 @@ TEST(EncapsulateSubgraphsTest, TwoFunctionsTwoOutside) { Node* key_constant2 = KeyPlaceholder("F2", b2.opts().WithName("F2_key_placeholder")); - Node* recv2 = RecvAtHost( - ops::NodeOut(key_constant2, 0), "host_compute_channel_F2_O1", - {DT_FLOAT}, b2.opts().WithName("outside_compilation_F2_O1_recv")); - Node* h = Binary(ops::NodeOut(call1, 1), recv2, b2.opts().WithName("H")); - Node* send2 = SendFromHost( - ops::NodeOut(key_constant2, 0), "host_compute_channel_F2_O1", {h}, - b2.opts().WithName("outside_compilation_F2_O1_send")); + Node* recv2 = RecvAtHost(ops::NodeOut(key_constant2, 0), "F2", "O1", + {DT_FLOAT}, b2.opts()); + Node* h = Binary(ops::NodeOut(call1, 1), recv2, + b2.opts() + .WithName("H") + .WithAttr("_encapsulate", "F2") + .WithAttr("_outside", "O1")); + Node* send2 = SendFromHost(ops::NodeOut(key_constant2, 0), "F2", "O1", {h}, + b2.opts()); Node* s2 = Sequencer( b2.opts().WithName("F2_sequencer").WithControlInputs({recv2, send2}), @@ -1311,12 +1331,14 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoInputs) { Node* a = InputShaped(b2.opts().WithName("A")); Node* b = Input(b2.opts().WithName("B")); - Node* e = Unary(a, b2.opts().WithName("E")); + Node* e = Unary(a, b2.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* send1 = SendFromHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, - b2.opts().WithName("outside_compilation_F1_O1_send")); + Node* send1 = + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, b2.opts()); Node* s1 = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInput(send1), "F1"); NodeBuilder node_builder1("F1", "F1", lib_def.get()); @@ -1395,12 +1417,14 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationControlInput) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); Node* recv1 = - RecvAtHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {}, b2.opts().WithName("outside_compilation_F1_O1_recv")); - Node* e = Unary(a, b2.opts().WithName("E").WithControlInput(recv1)); - Node* send1 = SendFromHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {e}, - b2.opts().WithName("outside_compilation_F1_O1_send")); + RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", {}, b2.opts()); + Node* e = Unary(a, b2.opts() + .WithName("E") + .WithControlInput(recv1) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send1 = + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, b2.opts()); Node* s1 = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInputs({recv1, send1}), "F1"); @@ -1470,10 +1494,12 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoOutputs) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv1 = RecvAtHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); - Node* e = Unary(recv1, b2.opts().WithName("E")); + Node* recv1 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT}, b2.opts()); + Node* e = Unary(recv1, b2.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); Node* s1 = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInput(recv1), "F1"); NodeBuilder node_builder1("F1", "F1", lib_def.get()); @@ -1547,15 +1573,14 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationControlOutput) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv1 = RecvAtHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); - Node* e = Unary(recv1, b2.opts().WithName("E")); - Node* send1 = SendFromHost(ops::NodeOut(key_constant, 0), - "host_compute_channel_F1_O1", {}, - b2.opts() - .WithName("outside_compilation_F1_O1_send") - .WithControlInput(e)); + Node* recv1 = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT}, b2.opts()); + Node* e = Unary(recv1, b2.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send1 = SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {}, + b2.opts().WithControlInput(e)); Node* s1 = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInputs({recv1, send1}), "F1"); @@ -1615,7 +1640,10 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationNoInputsOrOutputs) { Node* a = Input(b2.opts().WithName("A")); Node* b = Input(b2.opts().WithName("B")); - Node* e = Unary(a, b2.opts().WithName("E")); + Node* e = Unary(a, b2.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); NodeBuilder node_builder1("F1", "F1", lib_def.get()); node_builder1.Input(a).Input(b); Node* call1 = b2.opts().FinalizeBuilder(&node_builder1); @@ -1666,12 +1694,14 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { Node* key_constant = KeyPlaceholderShape(shape.opts().WithName("KnownShape/_0")); Node* known = KnownShape({2}, shape.opts().WithName("KnownShape/_1")); - Node* recv = RecvAtHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {DT_FLOAT}, - shape.opts().WithName("outside_compilation_F1_O1_recv")); - Node* e = BinaryUnknownShape(known, recv, shape.opts().WithName("E")); - SendFromHost(ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", - {e}, shape.opts().WithName("outside_compilation_F1_O1_send")); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT}, shape.opts()); + Node* e = BinaryUnknownShape(known, recv, + shape.opts() + .WithName("E") + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, shape.opts()); TF_EXPECT_OK( AddGraphDefToFunctionLibrary(shape, "F1_O1", &library_expected)); } @@ -1709,17 +1739,16 @@ TEST(EncapsulateSubgraphsTest, OutsideCompilationShapeInference) { Node* key_constant = KeyPlaceholder("F1", b2.opts().WithName("F1_key_placeholder")); - Node* recv = RecvAtHost( - ops::NodeOut(key_constant, 0), "host_compute_channel_F1_O1", {DT_FLOAT}, - b2.opts().WithName("outside_compilation_F1_O1_recv")); - Node* e = BinaryUnknownShape( - c, ops::NodeOut(recv, 0), - b2.opts().WithName("E").WithControlInputs({recv, b})); - Node* send = SendFromHost(ops::NodeOut(key_constant, 0), - "host_compute_channel_F1_O1", {e}, - b2.opts() - .WithName("outside_compilation_F1_O1_send") - .WithControlInput(e)); + Node* recv = RecvAtHost(ops::NodeOut(key_constant, 0), "F1", "O1", + {DT_FLOAT}, b2.opts()); + Node* e = BinaryUnknownShape(c, ops::NodeOut(recv, 0), + b2.opts() + .WithName("E") + .WithControlInputs({recv, b}) + .WithAttr("_encapsulate", "F1") + .WithAttr("_outside", "O1")); + Node* send = SendFromHost(ops::NodeOut(key_constant, 0), "F1", "O1", {e}, + b2.opts().WithControlInput(e)); Node* s = Sequencer( b2.opts().WithName("F1_sequencer").WithControlInputs({recv, send}), diff --git a/tensorflow/contrib/tpu/ops/replication_ops.cc b/tensorflow/contrib/tpu/ops/replication_ops.cc index cba71c6b98..3bdf7c2f83 100644 --- a/tensorflow/contrib/tpu/ops/replication_ops.cc +++ b/tensorflow/contrib/tpu/ops/replication_ops.cc @@ -27,6 +27,7 @@ REGISTER_OP("TPUReplicateMetadata") .Attr("topology: string = \"\"") .Attr("device_assignment: list(int) = []") .Attr("computation_shape: list(int) = []") + .Attr("host_compute_core: list(string) = []") .SetShapeFn(shape_inference::UnknownShape); REGISTER_OP("TPUReplicatedInput") @@ -68,6 +69,7 @@ REGISTER_OP("TPUReplicate") .Attr("num_replicas: int >= 1") .Attr("topology: string = \"\"") .Attr("device_assignment: list(int) = []") + .Attr("host_compute_core: list(string) = []") .Attr("computation_shape: list(int) = []") .Attr("Tinputs: list(type) >= 0") .Attr("Tbroadcast_inputs: list(type) >= 0") -- GitLab From b80960d8b7c87a3cf221cdbbb9c68c5970bfd3c7 Mon Sep 17 00:00:00 2001 From: Zhixian Yan Date: Thu, 29 Mar 2018 12:39:33 -0700 Subject: [PATCH 424/906] Add more tflite hosted models like resnet, inception-v4, nasnet. PiperOrigin-RevId: 190970367 --- tensorflow/contrib/lite/g3doc/models.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/g3doc/models.md b/tensorflow/contrib/lite/g3doc/models.md index 48f43d4fc4..d8134d5a00 100644 --- a/tensorflow/contrib/lite/g3doc/models.md +++ b/tensorflow/contrib/lite/g3doc/models.md @@ -1,7 +1,13 @@ # List of Hosted Models -* [Inception V3 2015](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_2015_2017_11_10.zip) -* [Inception V3 Slim 2016](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) +* [NASNet large](https://storage.googleapis.com/download.tensorflow.org/models/tflite/nasnet_large_2018_03_27.zip) +* [NASNet mobile](https://storage.googleapis.com/download.tensorflow.org/models/tflite/nasnet_mobile_2018_03_27.zip) +* [ResNet v2 101](https://storage.googleapis.com/download.tensorflow.org/models/tflite/resnet_v2_101_2018_03_27.zip) +* [ResNet v2 50](https://storage.googleapis.com/download.tensorflow.org/models/tflite/resnet_v2_50_2018_03_27.zip) +* [Inception ResNet v2](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_resnet_v2_2018_03_27.zip) +* [Inception v4](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v4_2018_03_27.zip) +* [Inception v3 2015](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_2015_2017_11_10.zip) +* [Inception v3 Slim 2016](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) * [Mobilenet 0.25 128 Float](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_0.25_128_float_2017_11_08.zip) * [Mobilenet 0.25 160 Float](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_0.25_160_float_2017_11_08.zip) * [Mobilenet 0.25 192 Float](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_0.25_192_float_2017_11_08.zip) -- GitLab From e58e4c754fa6145af2a411b940d8f7347a071b6f Mon Sep 17 00:00:00 2001 From: Jonathan Hseu Date: Thu, 29 Mar 2018 12:54:59 -0700 Subject: [PATCH 425/906] Minor adjustments to an error message. PiperOrigin-RevId: 190972253 --- tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py b/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py index 493d1848c0..eea57ed336 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_system_metadata.py @@ -72,9 +72,9 @@ def _query_tpu_system_metadata(master_address, run_config, tpu_core_count += 1 break except errors.DeadlineExceededError: - msg = ('Fail to connect Tensorflow master. It could be the TPU worker is ' - 'not ready (still under scheduling) or Tensorflow ' - 'master address is correct: got (%s).' % + msg = ('Failed to connect to the Tensorflow master. The TPU worker may ' + 'not be ready (still scheduling) or the Tensorflow master address ' + 'is incorrect: got (%s).' % (master_address)) # TODO(xiejw): For local or grpc master we might not need retry logic -- GitLab From d9e5f2754cabd9680d5481464a4085e79856eb78 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Thu, 29 Mar 2018 12:58:43 -0700 Subject: [PATCH 426/906] Avoid evaluating SaveSpec Tensors multiple times when executing eagerly The Saver now calls a SaveSpec callable once when saving and not at all when restoring. Previously saving evaluated the callable twice and restoring once (copying a variable's value each time). Requires a dtype be passed to a SaveSpec if its tensor is callable. PiperOrigin-RevId: 190972754 --- tensorflow/python/training/saver.py | 40 +++++++++++++++++++----- tensorflow/python/training/saver_test.py | 31 ++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/tensorflow/python/training/saver.py b/tensorflow/python/training/saver.py index cec581d997..e40b8d22ed 100644 --- a/tensorflow/python/training/saver.py +++ b/tensorflow/python/training/saver.py @@ -91,17 +91,27 @@ class BaseSaverBuilder(object): class SaveSpec(object): """Class used to describe tensor slices that need to be saved.""" - def __init__(self, tensor, slice_spec, name): + def __init__(self, tensor, slice_spec, name, dtype=None): """Creates a `SaveSpec` object. Args: tensor: the tensor to save or callable that produces a tensor to save. slice_spec: the slice to be saved. See `Variable.SaveSliceInfo`. name: the name to save the tensor under. + dtype: The data type of the Tensor. Required if `tensor` is callable. + Used for error checking in the restore op. """ self._tensor = tensor self.slice_spec = slice_spec self.name = name + if callable(self._tensor): + if dtype is None: + raise AssertionError( + "When passing a callable `tensor` to a SaveSpec, an explicit " + "dtype must be provided.") + self.dtype = dtype + else: + self.dtype = tensor.dtype @property def tensor(self): @@ -117,14 +127,27 @@ class BaseSaverBuilder(object): op: the "producer" object that this class wraps; it produces a list of tensors to save. E.g., a "Variable" object saving its backing tensor. specs: a list of SaveSpec, each element of which describes one tensor to - save under this object. + save under this object. All Tensors must be on the same device. name: the name to save the object under. """ self.op = op self.specs = specs self.name = name - # The device of this saveable. All tensors must be on the same device. - self.device = specs[0].tensor.device + self._device = None + + @property + def device(self): + """The device for SaveSpec Tensors.""" + # Note that SaveSpec.tensor runs Tensor-gathering ops when executing + # eagerly, making this call potentially very expensive. + # + # TODO(allenl): Consider another way to gather device information. Lower + # priority since this property isn't part of the normal save()/restore() + # workflow, but does come up when some alternative builders are passed to + # the Saver. + if self._device is None: + self._device = self.specs[0].tensor.device + return self._device def restore(self, restored_tensors, restored_shapes): """Restores this object from 'restored_tensors'. @@ -148,7 +171,7 @@ class BaseSaverBuilder(object): """SaveableObject implementation that handles Variables.""" def __init__(self, var, slice_spec, name): - spec = BaseSaverBuilder.SaveSpec(var, slice_spec, name) + spec = BaseSaverBuilder.SaveSpec(var, slice_spec, name, dtype=var.dtype) super(BaseSaverBuilder.VariableSaveable, self).__init__(var, [spec], name) def restore(self, restored_tensors, restored_shapes): @@ -186,7 +209,8 @@ class BaseSaverBuilder(object): raise ValueError( "Saveable is neither a resource variable nor a read operation." " Got: %s" % repr(var)) - spec = BaseSaverBuilder.SaveSpec(tensor, slice_spec, name) + spec = BaseSaverBuilder.SaveSpec(tensor, slice_spec, name, + dtype=var.dtype) super(BaseSaverBuilder.ResourceVariableSaveable, self).__init__( var, [spec], name) @@ -295,7 +319,7 @@ class BaseSaverBuilder(object): filename_tensor, [spec.name], [spec.slice_spec], - [spec.tensor.dtype])[0]) + [spec.dtype])[0]) return tensors # pylint: enable=unused-argument @@ -854,7 +878,7 @@ class BulkSaverBuilder(BaseSaverBuilder): restore_specs = [] for saveable in saveables: for spec in saveable.specs: - restore_specs.append((spec.name, spec.slice_spec, spec.tensor.dtype)) + restore_specs.append((spec.name, spec.slice_spec, spec.dtype)) names, slices, dtypes = zip(*restore_specs) # Load all tensors onto CPU 0 for compatibility with existing code. diff --git a/tensorflow/python/training/saver_test.py b/tensorflow/python/training/saver_test.py index d1c24b3930..14dda79979 100644 --- a/tensorflow/python/training/saver_test.py +++ b/tensorflow/python/training/saver_test.py @@ -2980,6 +2980,37 @@ class CheckpointableCompatibilityTests(test.TestCase): self.assertEqual(42., self.evaluate(v.non_dep_variable)) self.assertEqual(42., self.evaluate(v.mirrored)) + def testSingleTensorEvaluation(self): + + class _CountingSaveable(saver_module.BaseSaverBuilder.SaveableObject): + + def __init__(self, name): + self.eval_count = 0 + def _tensor(): + self.eval_count += 1 + return constant_op.constant([1.]) + dummy_op = constant_op.constant([2.]) + super(_CountingSaveable, self).__init__( + dummy_op, + [saver_module.BaseSaverBuilder.SaveSpec( + _tensor, "", name, dtype=dummy_op.dtype)], + name) + + def restore(self, restored_tensors, restored_shapes): + """Restore the same value into both variables.""" + pass + + with context.eager_mode(): + v = _CountingSaveable("foo") + saver = saver_module.Saver(var_list=[v]) + test_dir = self.get_temp_dir() + prefix = os.path.join(test_dir, "ckpt") + with self.test_session() as sess: + save_path = saver.save(sess, prefix) + self.assertEqual(1, v.eval_count) + saver.restore(sess, save_path) + self.assertEqual(1, v.eval_count) + if __name__ == "__main__": test.main() -- GitLab From a259ba951d3af9f62a0f95a881abf9ebaa45782b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 13:18:54 -0700 Subject: [PATCH 427/906] Fix docstring. PiperOrigin-RevId: 190975767 --- tensorflow/contrib/autograph/converters/break_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/autograph/converters/break_statements.py b/tensorflow/contrib/autograph/converters/break_statements.py index 721bc0ccd0..48026bccab 100644 --- a/tensorflow/contrib/autograph/converters/break_statements.py +++ b/tensorflow/contrib/autograph/converters/break_statements.py @@ -27,7 +27,7 @@ from tensorflow.contrib.autograph.pyct.static_analysis.annos import NodeAnno class BreakCanonicalizationTransformer(transformer.Base): - """Canonicalizes continue statements into additional conditionals.""" + """Canonicalizes break statements into additional conditionals.""" def __init__(self, context): super(BreakCanonicalizationTransformer, self).__init__(context) -- GitLab From eb2be37c12ae2b6c996f3f4c064e3d10f9565eab Mon Sep 17 00:00:00 2001 From: Yuefeng Zhou Date: Thu, 29 Mar 2018 13:22:45 -0700 Subject: [PATCH 428/906] Internal change. PiperOrigin-RevId: 190976338 --- tensorflow/python/layers/normalization.py | 76 +++++++++++------------ tensorflow/python/training/distribute.py | 10 +++ 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/tensorflow/python/layers/normalization.py b/tensorflow/python/layers/normalization.py index 29fb92ccb5..83b201e642 100644 --- a/tensorflow/python/layers/normalization.py +++ b/tensorflow/python/layers/normalization.py @@ -32,12 +32,12 @@ from tensorflow.python.framework import tensor_shape from tensorflow.python.layers import base from tensorflow.python.layers import utils from tensorflow.python.ops import array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops from tensorflow.python.ops import nn -from tensorflow.python.ops import gen_resource_variable_ops from tensorflow.python.ops import resource_variable_ops -from tensorflow.python.ops import math_ops -from tensorflow.python.ops import init_ops from tensorflow.python.ops import state_ops +from tensorflow.python.training import distribute as distribute_lib from tensorflow.python.training import moving_averages from tensorflow.python.util.tf_export import tf_export @@ -178,6 +178,11 @@ class BatchNormalization(base.Layer): self.renorm_clipping = renorm_clipping self.renorm_momentum = renorm_momentum + def _add_tower_local_variable(self, *args, **kwargs): + tower_context = distribute_lib.get_tower_context() + with tower_context.tower_local_var_scope('mean'): + return self.add_variable(*args, **kwargs) + def build(self, input_shape): input_shape = tensor_shape.TensorShape(input_shape) if not input_shape.ndims: @@ -305,14 +310,14 @@ class BatchNormalization(base.Layer): self._scope.set_partitioner(None) else: partitioner = None - self.moving_mean = self.add_variable( + self.moving_mean = self._add_tower_local_variable( name='moving_mean', shape=param_shape, dtype=param_dtype, initializer=self.moving_mean_initializer, trainable=False) - self.moving_variance = self.add_variable( + self.moving_variance = self._add_tower_local_variable( name='moving_variance', shape=param_shape, dtype=param_dtype, @@ -328,7 +333,7 @@ class BatchNormalization(base.Layer): # stack to be cleared. The nested ones use a `lambda` to set the desired # device and ignore any devices that may be set by the custom getter. def _renorm_variable(name, shape): - var = self.add_variable( + var = self._add_tower_local_variable( name=name, shape=shape, dtype=param_dtype, @@ -336,24 +341,19 @@ class BatchNormalization(base.Layer): trainable=False) return var - with ops.device(None): - device = ( - self.moving_mean.device if context.executing_eagerly() else - (lambda _: self.moving_mean.device)) - with ops.device(device): - self.renorm_mean = _renorm_variable('renorm_mean', param_shape) - self.renorm_mean_weight = _renorm_variable('renorm_mean_weight', ()) - # We initialize renorm_stddev to 0, and maintain the (0-initialized) - # renorm_stddev_weight. This allows us to (1) mix the average - # stddev with the minibatch stddev early in training, and (2) compute - # the unbiased average stddev by dividing renorm_stddev by the weight. - device = ( - self.moving_variance.device if context.executing_eagerly() else - (lambda _: self.moving_variance.device)) - with ops.device(device): - self.renorm_stddev = _renorm_variable('renorm_stddev', param_shape) - self.renorm_stddev_weight = _renorm_variable( - 'renorm_stddev_weight', ()) + with distribute_lib.get_distribution_strategy().colocate_vars_with( + self.moving_mean): + self.renorm_mean = _renorm_variable('renorm_mean', param_shape) + self.renorm_mean_weight = _renorm_variable('renorm_mean_weight', ()) + # We initialize renorm_stddev to 0, and maintain the (0-initialized) + # renorm_stddev_weight. This allows us to (1) mix the average + # stddev with the minibatch stddev early in training, and (2) compute + # the unbiased average stddev by dividing renorm_stddev by the weight. + with distribute_lib.get_distribution_strategy().colocate_vars_with( + self.moving_variance): + self.renorm_stddev = _renorm_variable('renorm_stddev', param_shape) + self.renorm_stddev_weight = _renorm_variable('renorm_stddev_weight', + ()) finally: if partitioner: self._scope.set_partitioner(partitioner) @@ -362,12 +362,11 @@ class BatchNormalization(base.Layer): def _assign_moving_average(self, variable, value, momentum): with ops.name_scope(None, 'AssignMovingAvg', [variable, value, momentum]) as scope: - with ops.colocate_with(variable): - decay = ops.convert_to_tensor(1.0 - momentum, name='decay') - if decay.dtype != variable.dtype.base_dtype: - decay = math_ops.cast(decay, variable.dtype.base_dtype) - update_delta = (variable - value) * decay - return state_ops.assign_sub(variable, update_delta, name=scope) + decay = ops.convert_to_tensor(1.0 - momentum, name='decay') + if decay.dtype != variable.dtype.base_dtype: + decay = math_ops.cast(decay, variable.dtype.base_dtype) + update_delta = (variable - value) * decay + return state_ops.assign_sub(variable, update_delta, name=scope) def _fused_batch_norm(self, inputs, training): """Returns the output of fused batch norm.""" @@ -473,16 +472,13 @@ class BatchNormalization(base.Layer): return array_ops.identity(var) return utils.smart_cond(training, _do_update, _fake_update) - with ops.colocate_with(self.moving_mean): - new_mean = _update_renorm_variable(self.renorm_mean, - self.renorm_mean_weight, - mean) - with ops.colocate_with(self.moving_variance): - new_stddev = _update_renorm_variable(self.renorm_stddev, - self.renorm_stddev_weight, - stddev) - # Make sqrt(moving_variance + epsilon) = new_stddev. - new_variance = math_ops.square(new_stddev) - self.epsilon + # TODO(yuefengz): colocate the operations + new_mean = _update_renorm_variable(self.renorm_mean, + self.renorm_mean_weight, mean) + new_stddev = _update_renorm_variable(self.renorm_stddev, + self.renorm_stddev_weight, stddev) + # Make sqrt(moving_variance + epsilon) = new_stddev. + new_variance = math_ops.square(new_stddev) - self.epsilon return (r, d, new_mean, new_variance) diff --git a/tensorflow/python/training/distribute.py b/tensorflow/python/training/distribute.py index f98872775a..d5106752dd 100644 --- a/tensorflow/python/training/distribute.py +++ b/tensorflow/python/training/distribute.py @@ -1082,6 +1082,16 @@ class _DefaultDistributionStrategy(DistributionStrategy): return _CurrentDistributionContext( self, variable_scope.variable_creator_scope(creator)) + def tower_local_var_scope(self, reduce_method): + """Does not set to resource variables.""" + def create_tower_local_variable(next_creator, *args, **kwargs): + _require_distribution_strategy_scope(self) + kwargs["tower_local_reduce_method"] = reduce_method + return next_creator(*args, **kwargs) + + _require_distribution_strategy_scope(self) + return variable_scope.variable_creator_scope(create_tower_local_variable) + def colocate_vars_with(self, colocate_with_variable): """Does not require `self.scope`.""" _require_distribution_strategy_scope(self) -- GitLab From ae94d2caaa713393d9c046f46e1ed7303ecf308c Mon Sep 17 00:00:00 2001 From: Nathan Burnham Date: Thu, 29 Mar 2018 16:26:07 -0400 Subject: [PATCH 429/906] Fixed a spelling error that broke the GANEstimator documentation example (#18097) Fixed a spelling error that broke the tfgan.estimator.GANEstimator documentation example --- .../contrib/gan/python/estimator/python/gan_estimator_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py index 082c42eba1..e3fc6bf0f0 100644 --- a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py +++ b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py @@ -88,8 +88,8 @@ class GANEstimator(estimator.Estimator): discriminator_fn=discriminator_fn, generator_loss_fn=tfgan.losses.wasserstein_generator_loss, discriminator_loss_fn=tfgan.losses.wasserstein_discriminator_loss, - generator_optimizer=tf.train.AdamOptimizier(0.1, 0.5), - discriminator_optimizer=tf.train.AdamOptimizier(0.1, 0.5)) + generator_optimizer=tf.train.AdamOptimizer(0.1, 0.5), + discriminator_optimizer=tf.train.AdamOptimizer(0.1, 0.5)) # Train estimator. gan_estimator.train(train_input_fn, steps) -- GitLab From 690ecae1f2519ed54693d51af0d28372a02ff31e Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Thu, 29 Mar 2018 16:26:19 -0400 Subject: [PATCH 430/906] Initial commit for the demo notebook (#18093) * Create touch.txt Dummy file to create the branch and directory structure * Add files via upload Initial commit * Delete touch.txt --- .../notebooks/dev_summit_2018_demo.ipynb | 1970 +++++++++++++++++ 1 file changed, 1970 insertions(+) create mode 100644 tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb diff --git a/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb b/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb new file mode 100644 index 0000000000..3129a39a4b --- /dev/null +++ b/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb @@ -0,0 +1,1970 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "Dev Summit 2018 - Autograph", + "version": "0.3.2", + "views": {}, + "default_view": {}, + "provenance": [ + { + "file_id": "1wCZUh73zTNs1jzzYjqoxMIdaBWCdKJ2K", + "timestamp": 1522238054357 + }, + { + "file_id": "1_HpC-RrmIv4lNaqeoslUeWaX8zH5IXaJ", + "timestamp": 1521743157199 + }, + { + "file_id": "1mjO2fQ2F9hxpAzw2mnrrUkcgfb7xSGW-", + "timestamp": 1520522344607 + } + ], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python2", + "display_name": "Python 2" + } + }, + "cells": [ + { + "metadata": { + "id": "g7nGs4mzVUHP", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# Experimental: TF Autograph\n", + "**TensorFlow Dev Summit, 2018.**\n", + "\n", + "This interactive notebook demonstrates **autograph**, an experimental source-code transformation library to automatically convert TF.Eager and Python code to TensorFlow graphs.\n", + "\n", + "**Note: this is pre-alpha software!** The notebook works best with Python 2, for now.\n", + "\n", + "> ![alt text](https://lh3.googleusercontent.com/QOvy0clmg7siaVKzwmSPAjicWWNQ0OeyaB16plDjSJMf35WD3vLjF6mz4CGrhSHw60HnlZPJjkyDCBzw5XOI0oBGSewyYw=s688)\n", + "\n", + "### Table of Contents\n", + "1. _Write Eager code that is fast and scalable._\n", + "2. _Case study: complex control flow._\n", + "3. _Case study: training MNIST with Keras._\n", + "4. _Case study: building an RNN._" + ] + }, + { + "metadata": { + "id": "uFcgBENZqkB2", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "# Install TensorFlow; note that Colab notebooks run remotely, on virtual\n", + "# instances provided by Google.\n", + "!pip install -U -q tf-nightly" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "Pa2qpEmoVOGe", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "import os\n", + "import time\n", + "\n", + "import tensorflow as tf\n", + "from tensorflow.contrib import autograph\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import six\n", + "\n", + "from google.colab import widgets" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "ZVKfj5ttVkqz", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 1. Write Eager code that is fast and scalable\n", + "\n", + "TF.Eager gives you more flexibility while coding, but at the cost of losing the benefits of TensorFlow graphs. For example, Eager does not currently support distributed training, exporting models, and a variety of memory and computation optimizations.\n", + "\n", + "Autograph gives you the best of both worlds: write your code in an Eager style, and we will automatically transform it into the equivalent TF graph code. The graph code can be executed eagerly (as a single op), included as part of a larger graph, or exported." + ] + }, + { + "metadata": { + "id": "snaZRFdWd9ym", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "For example, autograph can convert a function like this:" + ] + }, + { + "metadata": { + "id": "9__n8cSIeDnD", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def g(x):\n", + " if x > 0:\n", + " x = x * x\n", + " else:\n", + " x = 0\n", + " return x" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "gq0eQcuReHET", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "... into a TF graph-building function:" + ] + }, + { + "metadata": { + "id": "sELSn599ePUF", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 413 + }, + "outputId": "bb0c7216-1ca3-4da1-d1fb-589902cdcd1a", + "executionInfo": { + "status": "ok", + "timestamp": 1522345737505, + "user_tz": 240, + "elapsed": 243, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "print(autograph.to_code(g))" + ], + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "text": [ + "from __future__ import print_function\n", + "import tensorflow as tf\n", + "from tensorflow.contrib.autograph.impl import api as autograph_api\n", + "from tensorflow.contrib.autograph import utils as autograph_utils\n", + "\n", + "def tf__g(x):\n", + " with tf.name_scope('g'):\n", + "\n", + " def if_true():\n", + " with tf.name_scope('if_true'):\n", + " x_1, = x,\n", + " x_1 = x_1 * x_1\n", + " return x_1,\n", + "\n", + " def if_false():\n", + " with tf.name_scope('if_false'):\n", + " x_1, = x,\n", + " x_1 = 0\n", + " return x_1,\n", + " x = autograph_utils.run_cond(tf.greater(x, 0), if_true, if_false)\n", + " return x\n", + "\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "j74n-8hEe6dk", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "You can then use the converted function as you would any regular TF op -- you can pass `Tensor` arguments and it will return `Tensor`s:" + ] + }, + { + "metadata": { + "id": "AkVaY0-dfEbH", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 53 + }, + "outputId": "4ffe3757-c44d-424c-c2a8-7ddc973bfcce", + "executionInfo": { + "status": "ok", + "timestamp": 1522345737841, + "user_tz": 240, + "elapsed": 257, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "tf_g = autograph.to_graph(g)\n", + "\n", + "with tf.Graph().as_default(): \n", + "\n", + " g_ops = tf_g(tf.constant(9))\n", + "\n", + " with tf.Session() as sess:\n", + " tf_g_result = sess.run(g_ops)\n", + "\n", + " print('g(9) = %s' % g(9))\n", + " print('tf_g(9) = %s' % tf_g_result)" + ], + "execution_count": 5, + "outputs": [ + { + "output_type": "stream", + "text": [ + "g(9) = 81\n", + "tf_g(9) = 81\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "trrHQBM1VnD0", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 2. Case study: complex control flow\n", + "\n", + "Autograph can convert a large chunk of the Python language into graph-equivalent code, and we're adding new supported language features all the time. In this section, we'll give you a taste of some of the functionality in autograph.\n", + "Autograph will automatically convert most Python control flow statements into their correct graph equivalent.\n", + " " + ] + }, + { + "metadata": { + "id": "u0YG3DPgZxoW", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "We support common statements like `while`, `for`, `if`, `break`, `return` and more. You can even nest them as much as you like. Imagine trying to write the graph version of this code by hand:" + ] + }, + { + "metadata": { + "id": "xJYDzOcrZ8pI", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "outputId": "6c244ee4-b141-4ad6-eefa-cfffa71f33c6", + "executionInfo": { + "status": "ok", + "timestamp": 1522345738402, + "user_tz": 240, + "elapsed": 483, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def sum_even(numbers):\n", + " s = 0\n", + " for n in numbers:\n", + " if n % 2 > 0:\n", + " continue\n", + " s += n\n", + " return s\n", + "\n", + "\n", + "tf_sum_even = autograph.to_graph(sum_even)\n", + "\n", + "with tf.Graph().as_default(): \n", + " with tf.Session() as sess:\n", + " result = sess.run(tf_sum_even(tf.constant([10, 12, 15, 20])))\n", + "\n", + " print('Sum of even numbers: %s' % result)" + ], + "execution_count": 6, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Sum of even numbers: 42\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "_YXo4KOcbKrn", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Try replacing the `continue` in the above code with `break` -- Autograph supports that as well!" + ] + }, + { + "metadata": { + "id": "xHmC0rBIavW_", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "The Python code above is much more readable than the matching graph code. Autograph takes care of tediously converting every piece of Python code into the matching TensorFlow graph version for you, so that you can quickly write maintainable code, but still benefit from the optimizations and deployment benefits of graphs." + ] + }, + { + "metadata": { + "id": "UEHWGpBXbS7g", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Let's try some other useful Python constructs, like `print` and `assert`. We automatically convert Python `assert` statements into the equivalent `tf.Assert` code. " + ] + }, + { + "metadata": { + "id": "qUU57xlEbauI", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 53 + }, + "outputId": "add3db4a-2077-4dd5-f7a7-a5b5a4529c26", + "executionInfo": { + "status": "ok", + "timestamp": 1522345738697, + "user_tz": 240, + "elapsed": 253, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def f(x):\n", + " assert x != 0, 'Do not pass zero!'\n", + " return x * x\n", + "\n", + "tf_f = autograph.to_graph(f)\n", + "with tf.Graph().as_default(): \n", + " with tf.Session() as sess:\n", + " try:\n", + " print(sess.run(tf_f(tf.constant(0))))\n", + " except tf.errors.InvalidArgumentError as e:\n", + " print('Got error message: %s' % e.message)" + ], + "execution_count": 7, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Got error message: assertion failed: [Do not pass zero!]\n", + "\t [[Node: f/Assert/Assert = Assert[T=[DT_STRING], summarize=3, _device=\"/job:localhost/replica:0/task:0/device:CPU:0\"](f/NotEqual, f/Assert/Assert/data_0)]]\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "w5hBZaVJbck4", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "You can also use `print` functions in-graph:" + ] + }, + { + "metadata": { + "id": "6NdzRKLEboRv", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "outputId": "fb82dfc3-790f-4127-87f6-361805be9e9b", + "executionInfo": { + "status": "ok", + "timestamp": 1522345739013, + "user_tz": 240, + "elapsed": 247, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def print_sign(n):\n", + " if n >= 0:\n", + " print(n, 'is positive!')\n", + " else:\n", + " print(n, 'is negative!')\n", + " return n\n", + "\n", + "\n", + "tf_print_sign = autograph.to_graph(print_sign)\n", + "with tf.Graph().as_default():\n", + " with tf.Session() as sess:\n", + " sess.run(tf_print_sign(tf.constant(1)))" + ], + "execution_count": 8, + "outputs": [ + { + "output_type": "stream", + "text": [ + "1 is positive!\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "9u_Z3i3AivLA", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "We can convert lists to TensorArray, so appending to lists also works, with a few modifications:" + ] + }, + { + "metadata": { + "id": "MjhCQJVuiTNR", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "outputId": "dc320b87-595b-4392-d29c-994486fd8a0a", + "executionInfo": { + "status": "ok", + "timestamp": 1522345744470, + "user_tz": 240, + "elapsed": 5391, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def f(n):\n", + " numbers = []\n", + " # We ask you to tell us about the element dtype.\n", + " autograph.utils.set_element_type(numbers, tf.int32)\n", + " for i in range(n):\n", + " numbers.append(i)\n", + " return numbers.stack() # Stack the list so that it can be used as a Tensor\n", + "\n", + "\n", + "tf_f = autograph.to_graph(f)\n", + "with tf.Graph().as_default():\n", + " with tf.Session() as sess:\n", + " print(sess.run(tf_f(tf.constant(5))))" + ], + "execution_count": 9, + "outputs": [ + { + "output_type": "stream", + "text": [ + "[0 1 2 3 4]\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "UdG8ZFrkTAF2", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "And all of these functionalities, and more, can be composed into more complicated code:\n" + ] + }, + { + "metadata": { + "id": "DVs6wt8NKaGQ", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 1 + } + ], + "base_uri": "https://localhost:8080/", + "height": 53 + }, + "cellView": "code", + "outputId": "0a4b8d08-8f65-4bbc-85ba-dc4c60563519", + "executionInfo": { + "status": "ok", + "timestamp": 1522345745186, + "user_tz": 240, + "elapsed": 658, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def print_primes(n):\n", + " \"\"\"Returns all the prime numbers less than n.\"\"\"\n", + " assert n > 0\n", + " \n", + " primes = []\n", + " autograph.utils.set_element_type(primes, tf.int32)\n", + " for i in range(2, n):\n", + " is_prime = True\n", + " for k in range(2, i):\n", + " if i % k == 0:\n", + " is_prime = False\n", + " break\n", + " if not is_prime:\n", + " continue\n", + " primes.append(i)\n", + " all_primes = primes.stack()\n", + "\n", + " print('The prime numbers less than', n, 'are:')\n", + " print(all_primes)\n", + " return tf.no_op()\n", + "\n", + " \n", + "tf_print_primes = autograph.to_graph(print_primes)\n", + "with tf.Graph().as_default(): \n", + " with tf.Session() as sess:\n", + " n = tf.constant(50)\n", + " sess.run(tf_print_primes(n))" + ], + "execution_count": 10, + "outputs": [ + { + "output_type": "stream", + "text": [ + "The prime numbers less than 50 are:\n", + "[ 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47]\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "JQ8kQT99VqDk", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 3. Case study: training MNIST with Keras\n", + "\n", + "As we've seen, writing control flow in Autograph is easy. So running a training loop in graph should be easy as well!\n", + "\n", + "Here, we show an example of such a training loop for a simple Keras model that trains on MNIST." + ] + }, + { + "metadata": { + "id": "0CrtGWgwuLJr", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "import gzip\n", + "import shutil\n", + "\n", + "from six.moves import urllib\n", + "\n", + "\n", + "def download(directory, filename):\n", + " filepath = os.path.join(directory, filename)\n", + " if tf.gfile.Exists(filepath):\n", + " return filepath\n", + " if not tf.gfile.Exists(directory):\n", + " tf.gfile.MakeDirs(directory)\n", + " url = 'https://storage.googleapis.com/cvdf-datasets/mnist/' + filename + '.gz'\n", + " zipped_filepath = filepath + '.gz'\n", + " print('Downloading %s to %s' % (url, zipped_filepath))\n", + " urllib.request.urlretrieve(url, zipped_filepath)\n", + " with gzip.open(zipped_filepath, 'rb') as f_in, open(filepath, 'wb') as f_out:\n", + " shutil.copyfileobj(f_in, f_out)\n", + " os.remove(zipped_filepath)\n", + " return filepath\n", + "\n", + "\n", + "def dataset(directory, images_file, labels_file):\n", + " images_file = download(directory, images_file)\n", + " labels_file = download(directory, labels_file)\n", + "\n", + " def decode_image(image):\n", + " # Normalize from [0, 255] to [0.0, 1.0]\n", + " image = tf.decode_raw(image, tf.uint8)\n", + " image = tf.cast(image, tf.float32)\n", + " image = tf.reshape(image, [784])\n", + " return image / 255.0\n", + "\n", + " def decode_label(label):\n", + " label = tf.decode_raw(label, tf.uint8)\n", + " label = tf.reshape(label, [])\n", + " return tf.to_int32(label)\n", + "\n", + " images = tf.data.FixedLengthRecordDataset(\n", + " images_file, 28 * 28, header_bytes=16).map(decode_image)\n", + " labels = tf.data.FixedLengthRecordDataset(\n", + " labels_file, 1, header_bytes=8).map(decode_label)\n", + " return tf.data.Dataset.zip((images, labels))\n", + "\n", + "\n", + "def mnist_train(directory):\n", + " return dataset(directory, 'train-images-idx3-ubyte',\n", + " 'train-labels-idx1-ubyte')\n", + "\n", + "def mnist_test(directory):\n", + " return dataset(directory, 't10k-images-idx3-ubyte', 't10k-labels-idx1-ubyte')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "2zu1U9Nqir6L", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "First, we'll define a small three-layer neural network using the Keras API" + ] + }, + { + "metadata": { + "id": "x_MU13boiok2", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def mlp_model(input_shape):\n", + " model = tf.keras.Sequential([\n", + " tf.keras.layers.Dense(100, activation='relu', input_shape=input_shape),\n", + " tf.keras.layers.Dense(100, activation='relu'),\n", + " tf.keras.layers.Dense(10, activation='softmax')])\n", + " model.build()\n", + " return model" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "Wuqg3H8mi0Xj", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Let's connect the model definition (here abbreviated as `m`) to a loss function, so that we can train our model." + ] + }, + { + "metadata": { + "id": "W51sfbONiz_5", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def predict(m, x, y):\n", + " y_p = m(x)\n", + " losses = tf.keras.losses.categorical_crossentropy(y, y_p)\n", + " l = tf.reduce_mean(losses)\n", + " accuracies = tf.keras.metrics.categorical_accuracy(y, y_p)\n", + " accuracy = tf.reduce_mean(accuracies)\n", + " return l, accuracy" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "035tNWQki9tr", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Now the final piece of the problem specification (before loading data, and clicking everything together) is backpropagating the loss through the model, and optimizing the weights using the gradient." + ] + }, + { + "metadata": { + "id": "CsAD0ajbi9iZ", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def fit(m, x, y, opt):\n", + " l, accuracy = predict(m, x, y)\n", + " opt.minimize(l)\n", + " return l, accuracy" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "PcVRIacKjSwb", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "These are some utility functions to download data and generate batches for training" + ] + }, + { + "metadata": { + "id": "RVw57HdTjPzi", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def setup_mnist_data(is_training, hp, batch_size):\n", + " if is_training:\n", + " ds = mnist_train('/tmp/autograph_mnist_data')\n", + " ds = ds.shuffle(batch_size * 10)\n", + " else:\n", + " ds = mnist_test('/tmp/autograph_mnist_data')\n", + " ds = ds.repeat()\n", + " ds = ds.batch(batch_size)\n", + " return ds\n", + "\n", + "def get_next_batch(ds):\n", + " itr = ds.make_one_shot_iterator()\n", + " image, label = itr.get_next()\n", + " x = tf.to_float(tf.reshape(image, (-1, 28 * 28)))\n", + " y = tf.one_hot(tf.squeeze(label), 10)\n", + " return x, y" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "2zEJH5XNjgFz", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "This function specifies the main training loop. We instantiate the model (using the code above), instantiate an optimizer (here we'll use SGD with momentum, nothing too fancy), and we'll instantiate some lists to keep track of training and test loss and accuracy over time.\n", + "\n", + "In the loop inside this function, we'll grab a batch of data, apply an update to the weights of our model to improve its performance, and then record its current training loss and accuracy. Every so often, we'll log some information about training as well." + ] + }, + { + "metadata": { + "id": "UUI0566FjZPx", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def train(train_ds, test_ds, hp):\n", + " m = mlp_model((28 * 28,))\n", + " opt = tf.train.MomentumOptimizer(hp.learning_rate, 0.9)\n", + " train_losses = []\n", + " train_losses = autograph.utils.set_element_type(train_losses, tf.float32)\n", + " test_losses = []\n", + " test_losses = autograph.utils.set_element_type(test_losses, tf.float32)\n", + " train_accuracies = []\n", + " train_accuracies = autograph.utils.set_element_type(train_accuracies,\n", + " tf.float32)\n", + " test_accuracies = []\n", + " test_accuracies = autograph.utils.set_element_type(test_accuracies,\n", + " tf.float32)\n", + " i = tf.constant(0)\n", + " while i < hp.max_steps:\n", + " train_x, train_y = get_next_batch(train_ds)\n", + " test_x, test_y = get_next_batch(test_ds)\n", + " step_train_loss, step_train_accuracy = fit(m, train_x, train_y, opt)\n", + " step_test_loss, step_test_accuracy = predict(m, test_x, test_y)\n", + " if i % (hp.max_steps // 10) == 0:\n", + " print('Step', i, 'train loss:', step_train_loss, 'test loss:',\n", + " step_test_loss, 'train accuracy:', step_train_accuracy,\n", + " 'test accuracy:', step_test_accuracy)\n", + " train_losses.append(step_train_loss)\n", + " test_losses.append(step_test_loss)\n", + " train_accuracies.append(step_train_accuracy)\n", + " test_accuracies.append(step_test_accuracy)\n", + " i += 1\n", + " return (train_losses.stack(), test_losses.stack(), train_accuracies.stack(),\n", + " test_accuracies.stack())" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "cYiUQ1ppkHzk", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Everything is ready to go, let's train the model and plot its performance!" + ] + }, + { + "metadata": { + "id": "K1m8TwOKjdNd", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 13 + }, + { + "item_id": 14 + }, + { + "item_id": 15 + } + ], + "base_uri": "https://localhost:8080/", + "height": 988 + }, + "outputId": "f9d3eef3-5bea-45c1-ddf9-4edee73e4436", + "executionInfo": { + "status": "ok", + "timestamp": 1522345800262, + "user_tz": 240, + "elapsed": 52391, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "with tf.Graph().as_default():\n", + " hp = tf.contrib.training.HParams(\n", + " learning_rate=0.05,\n", + " max_steps=500,\n", + " )\n", + " train_ds = setup_mnist_data(True, hp, 50)\n", + " test_ds = setup_mnist_data(False, hp, 1000)\n", + " tf_train = autograph.to_graph(train)\n", + " (train_losses, test_losses, train_accuracies,\n", + " test_accuracies) = tf_train(train_ds, test_ds, hp)\n", + "\n", + " with tf.Session() as sess:\n", + " sess.run(tf.global_variables_initializer())\n", + " (train_losses, test_losses, train_accuracies,\n", + " test_accuracies) = sess.run([train_losses, test_losses, train_accuracies,\n", + " test_accuracies])\n", + " plt.title('MNIST train/test losses')\n", + " plt.plot(train_losses, label='train loss')\n", + " plt.plot(test_losses, label='test loss')\n", + " plt.legend()\n", + " plt.xlabel('Training step')\n", + " plt.ylabel('Loss')\n", + " plt.show()\n", + " plt.title('MNIST train/test accuracies')\n", + " plt.plot(train_accuracies, label='train accuracy')\n", + " plt.plot(test_accuracies, label='test accuracy')\n", + " plt.legend(loc='lower right')\n", + " plt.xlabel('Training step')\n", + " plt.ylabel('Accuracy')\n", + " plt.show()" + ], + "execution_count": 17, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Downloading https://storage.googleapis.com/cvdf-datasets/mnist/train-images-idx3-ubyte.gz to /tmp/autograph_mnist_data/train-images-idx3-ubyte.gz\n", + "Downloading https://storage.googleapis.com/cvdf-datasets/mnist/train-labels-idx1-ubyte.gz to /tmp/autograph_mnist_data/train-labels-idx1-ubyte.gz\n", + "Downloading https://storage.googleapis.com/cvdf-datasets/mnist/t10k-images-idx3-ubyte.gz to /tmp/autograph_mnist_data/t10k-images-idx3-ubyte.gz\n", + "Downloading https://storage.googleapis.com/cvdf-datasets/mnist/t10k-labels-idx1-ubyte.gz to /tmp/autograph_mnist_data/t10k-labels-idx1-ubyte.gz\n", + "Step 0 train loss: 2.244329 test loss: 2.2499208 train accuracy: 0.12 test accuracy: 0.161\n", + "Step 50 train loss: 0.64771986 test loss: 0.56013924 train accuracy: 0.82 test accuracy: 0.836\n", + "Step 100 train loss: 0.49011207 test loss: 0.42143965 train accuracy: 0.84 test accuracy: 0.879\n", + "Step 150 train loss: 0.3768609 test loss: 0.39319593 train accuracy: 0.88 test accuracy: 0.883\n", + "Step 200 train loss: 0.36007702 test loss: 0.37089333 train accuracy: 0.9 test accuracy: 0.881\n", + "Step 250 train loss: 0.182115 test loss: 0.28543878 train accuracy: 0.94 test accuracy: 0.915\n", + "Step 300 train loss: 0.2119576 test loss: 0.22305593 train accuracy: 0.92 test accuracy: 0.93\n", + "Step 350 train loss: 0.12932214 test loss: 0.29057172 train accuracy: 0.96 test accuracy: 0.906\n", + "Step 400 train loss: 0.22937602 test loss: 0.2200287 train accuracy: 0.92 test accuracy: 0.925\n", + "Step 450 train loss: 0.23444137 test loss: 0.19857481 train accuracy: 0.94 test accuracy: 0.94\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\n73znO7j//vtzPh+Px3HllVd6wmLr1q34+OOPceyxx/bquFRVRTgczllRt7a2oqKiAvvvvz80TXMq\n0EgkUtC+V69ejYMOOgh+vz/rvY488kg0NTXh448/BgA0NDTgZz/7GYQQ+PWvf4133nkHgBmSY8eO\nhSRJ3R6r7YgjjkBzczNWrVoFwBxfMH78eEycODHvfvM9/vWvfx0vvPACYrEYAOCpp57C888/j9bW\nVnz/+99HOByGLMs48sgjezXWgGgwsPIm6gNJknDaaachFotBlnu+Bv7BD36Ap59+Oudze++9N/70\npz/hvvvuw5w5cyCEQFVVFX75y196RqAX4phjjsEf/vAHTJs2DW+++abnuYMPPhjTp0/HzJkzMWbM\nGPziF7/Ahx9+iNmzZ+O///u/e9y3fYtYvve67777cOuttyISicDn8+Gqq66CJEm48MIL8etf/xq3\n3norhBCYMWMGpkyZgh07dnheryhK1ntWVFTgnnvuwa233opoNIrRo0fjrrvu6na/I0eOzPk4AGzc\nuBHnnHMOADPYb7vtNowePRrTpk3Dt771LSiKAp/Ph9tuu61X551ooElcz5uIiGhoYbM5ERHREMPw\nJiIiGmIY3kREREMMw5uIiGiIYXgTERENMUPmVrGmpq6i7m/UqAq0tUWLus89Ec9j//Ec9h/PYXHw\nPPZfsc9hbW11zsf32MpbVbPvKaXe43nsP57D/uM5LA6ex/4bqHO4x4Y3ERHRUMXwJiIiGmIY3kRE\nREMMw5uIiGiIYXgTERENMQxvIiKiIYbhTURENMQwvImIaNh6443XCt723nvvxI4d23vc7sMP38cN\nN/y8P4fVbwxvIiIalnbu3IFXX11c8PZXXXUt9t57QgmPqHiGzPSoREREvXHXXb/D+vXr8Mgjc2EY\nBnbs2I6dO3fgnnv+iN/+9jdoampELBbDD35wOaZOnYYf//hyXHPNz7F06WuIRML44out2L59G668\n8lpMmTI153u89toSzJv3dyiKgoMOOgS33XYL6us34M47fwefzwe/349bbvktdu7cnvVYdXXuqU8L\nsceGd0c4gfc3NOLYg+sG+1CIiIa9f7z+GVZuaCzqPo87uA7nz5ic9/l///fZeO65f+D7378MDz/8\nIDQthT/+8c9oa2vF8cd/DWeccRa2b9+GG2/8BaZOneZ5bWPjbvzhD/fh3XeX44UXns0Z3tFoFA89\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAe8AAAFnCAYAAACPasF4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzsvXe8XVWZ///e5dTba3pCQiAJCSWE\nIJGmoSSgjsg4gmCb4Tf+dCwURUdEQXGs41gYFQvDiIyIiKIIJIAgEBJCgJBKertpt59z76m7fv9Y\nu55zboiQBCL783rllXt2WXvttfden6et55Fs27aJECFChAgRIhw1kF/vDkSIECFChAgR/jZE5B0h\nQoQIESIcZYjIO0KECBEiRDjKEJF3hAgRIkSIcJQhIu8IESJEiBDhKENE3hEiRIgQIcJRhoi8I7yp\nMW3aND796U9Xbf/iF7/ItGnTQsfdcMMNoWOWL1/OBz/4QQB2797NCSec4O3btWsXH/vYx1iwYAEL\nFizgkksu4bHHHgPgpptuYuHChSxcuJCZM2fy9re/3fudy+VC19A0jfvvv/9vvq/Vq1dz1VVXHdSx\nDzzwAF/72tde9bVcvNbz3wi46667+P73v/96dyNChFeE+np3IEKE1xsbN24kl8tRX18PCBJas2ZN\n1XErVqxg/fr1IZIeCZ/97Gd597vfzW233QbAqlWr+PCHP8zDDz/MV77yFe+4+fPn8+1vf5vTTjut\nZjvr16/n/vvv55JLLvmb7umkk07i9ttvP6hjly5dyvnnn/+qr+XitZ7/RsAHPvCB17sLESIcFCLN\nO8KbHm95y1t49NFHvd9LlizhxBNPrDruuuuu4+tf//pBtblp0yZOPvlk7/fJJ5/M4sWLGT169EH3\nq6+vj09+8pO89NJLXHHFFYCwAPz0pz9lwYIFmKbJypUrufTSS1m4cCEXX3wxS5cuBYRV4IILLgDg\n1ltv5atf/Sqf+MQnOO+883jve99LT0+Pd53ly5czffr0qmu98MIL/OM//iMXXHAB73vf++jq6gKg\nu7ubD3/4w1x88cWcf/75fO9736vZ18p7ueqqq1i4cCHz58/njjvu8PatXbuWSy+9lAULFvCBD3zA\nu85I26dNm8b+/fu9893fy5cv5/LLL+fqq6/mM5/5DAD33nsvF110ERdeeCFXXnkle/bsAcC2bb7x\njW8wf/58FixYwC9+8QtvrL74xS8CsH///pD15MknnwTAMAy++MUvsmDBAi644AI++clPVllMIkQ4\n3IjIO8KbHhdddBF//vOfvd8PPvggCxcurHmcbdssWrToFds855xz+PSnP82dd97J1q1bARg1ahSS\nJB10v9rb27nuuus45ZRT+PWvf+1tt22bxYsXoygKX/7yl7nqqqtYtGgRH/3oR7nppptqtrVo0SJu\nuOEGHnvsMdra2rjvvvsA2Lp1Kx0dHYwbNy50rVwux8c//nGuu+46Hn30UT70oQ9x9dVXA/C///u/\nzJ07l4ceeogHHniArq4uLMuq2VcXP/nJTxg/fjyLFi3il7/8Jd/97nfZt28fIISiq6++msWLF3P+\n+edzyy23HHD7gbB+/Xouv/xyvvvd79Lf389Xv/pV7rjjDh555BEmTpzIj3/8YwD+9Kc/sXr1ahYv\nXsx9993HXXfdxerVq0Ntff7zn2f69OksXryYn/3sZ3zuc59jcHCQJUuWsHv3bhYtWsQjjzzC1KlT\nWbly5Sv2LUKEQ4mIvCO86XH66aezefNm+vv7KRaLrFy5knnz5tU89oYbbuA///M/KZfLB2zzO9/5\nDldeeSUPPPAA73znO5k/fz533333Ienv2972Nu/v+++/n4suugiAOXPmeNppJU477TTGjRuHJEnM\nmDHDI85ly5bVvNcXXniBUaNGceaZZwLwzne+k127drF3717a2tpYsmQJzz//PPF4nP/6r/+is7Pz\ngH2+8cYb+dKXvgTAhAkT6OjoYPfu3Wzfvp3BwUHOPfdcQJitb7311hG3vxKSyaR3P21tbbzwwgue\nteO0007zxuepp55iwYIFxGIx6uvreeihh0LWlkKhwPLly/nIRz4CwKRJk5gzZw5PPvkkra2tbN26\nlUcffZRiscg111zD2Wef/Yp9ixDhUCLyeUd400NRFC688EIefvhhWltbOeuss1DV2p/GzJkzmTt3\nLnfccQezZ88esc1EIsFVV13FVVddxdDQEIsWLeLrX/8648ePf80TfXNzs/f3Aw88wJ133kk+n8ey\nLEYqVdDQ0OD9rSgKpmkC8Mwzz3gEFcTQ0BBdXV0hC0Q8HmdgYICPfOQjWJbFV77yFXp6erjyyiv5\n1Kc+dcA+r1mzxtO2ZVmmt7cXy7IYHBwM9U1VVVRVHXH7K6Gpqcn72zRNfvjDH/L4449jmib5fJ7J\n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\nYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "metadata": { + "id": "HNqUFL4deCsL", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 4. Case study: building an RNN\n" + ] + }, + { + "metadata": { + "id": "YkC1k4HEQ7rw", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "In this exercise we build and train a model similar to the RNNColorbot model that was used in the main Eager notebook. The model is adapted for converting and training in graph mode." + ] + }, + { + "metadata": { + "id": "7nkPDl5CTCNb", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "To get started, we load the colorbot dataset. The code is identical to that used in the other exercise and its details are unimportant." + ] + }, + { + "metadata": { + "id": "A0uREmVXCQEw", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def parse(line):\n", + " \"\"\"Parses a line from the colors dataset.\n", + " \n", + " Args:\n", + " line: A comma-separated string containing four items:\n", + " color_name, red, green, and blue, representing the name and\n", + " respectively the RGB value of the color, as an integer\n", + " between 0 and 255.\n", + "\n", + " Returns:\n", + " A tuple of three tensors (rgb, chars, length), of shapes: (batch_size, 3),\n", + " (batch_size, max_sequence_length, 256) and respectively (batch_size).\n", + " \"\"\"\n", + " items = tf.string_split([line], \",\").values\n", + " rgb = tf.string_to_number(items[1:], out_type=tf.float32) / 255.0\n", + " color_name = items[0]\n", + " chars = tf.one_hot(tf.decode_raw(color_name, tf.uint8), depth=256)\n", + " length = tf.cast(tf.shape(chars)[0], dtype=tf.int64)\n", + " return rgb, chars, length\n", + "\n", + "\n", + "def maybe_download(filename, work_directory, source_url):\n", + " \"\"\"Downloads the data from source url.\"\"\"\n", + " if not tf.gfile.Exists(work_directory):\n", + " tf.gfile.MakeDirs(work_directory)\n", + " filepath = os.path.join(work_directory, filename)\n", + " if not tf.gfile.Exists(filepath):\n", + " temp_file_name, _ = six.moves.urllib.request.urlretrieve(source_url)\n", + " tf.gfile.Copy(temp_file_name, filepath)\n", + " with tf.gfile.GFile(filepath) as f:\n", + " size = f.size()\n", + " print('Successfully downloaded', filename, size, 'bytes.')\n", + " return filepath\n", + "\n", + "\n", + "def load_dataset(data_dir, url, batch_size, training=True):\n", + " \"\"\"Loads the colors data at path into a tf.PaddedDataset.\"\"\"\n", + " path = maybe_download(os.path.basename(url), data_dir, url)\n", + " dataset = tf.data.TextLineDataset(path)\n", + " dataset = dataset.skip(1)\n", + " dataset = dataset.map(parse)\n", + " dataset = dataset.cache()\n", + " dataset = dataset.repeat()\n", + " if training:\n", + " dataset = dataset.shuffle(buffer_size=3000)\n", + " dataset = dataset.padded_batch(batch_size, padded_shapes=([None], [None, None], []))\n", + " return dataset\n", + "\n", + "\n", + "train_url = \"https://raw.githubusercontent.com/random-forests/tensorflow-workshop/master/extras/colorbot/data/train.csv\"\n", + "test_url = \"https://raw.githubusercontent.com/random-forests/tensorflow-workshop/master/extras/colorbot/data/test.csv\"\n", + "data_dir = \"tmp/rnn/data\"" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "waZ89t3DTUla", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Next, we set up the RNNColobot model, which is very similar to the one we used in the main exercise.\n", + "\n", + "Autograph doesn't fully support classes yet (but it will soon!), so we'll write the model using simple functions." + ] + }, + { + "metadata": { + "id": "9v8AJouiC44V", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def model_components():\n", + " lower_cell = tf.contrib.rnn.LSTMBlockCell(256)\n", + " lower_cell.build(tf.TensorShape((None, 256)))\n", + " upper_cell = tf.contrib.rnn.LSTMBlockCell(128)\n", + " upper_cell.build(tf.TensorShape((None, 256)))\n", + " relu_layer = tf.layers.Dense(3, activation=tf.nn.relu)\n", + " relu_layer.build(tf.TensorShape((None, 128)))\n", + " return lower_cell, upper_cell, relu_layer\n", + "\n", + "\n", + "def rnn_layer(chars, cell, batch_size, training):\n", + " \"\"\"A simple RNN layer.\n", + " \n", + " Args:\n", + " chars: A Tensor of shape (max_sequence_length, batch_size, input_size)\n", + " cell: An object of type tf.contrib.rnn.LSTMBlockCell\n", + " batch_size: Int, the batch size to use\n", + " training: Boolean, whether the layer is used for training\n", + "\n", + " Returns:\n", + " A Tensor of shape (max_sequence_length, batch_size, output_size).\n", + " \"\"\"\n", + " hidden_outputs = []\n", + " autograph.utils.set_element_type(hidden_outputs, tf.float32)\n", + " state, output = cell.zero_state(batch_size, tf.float32)\n", + " n = tf.shape(chars)[0]\n", + " i = 0\n", + " while i < n:\n", + " ch = chars[i]\n", + " cell_output, (state, output) = cell.call(ch, (state, output))\n", + " hidden_outputs.append(cell_output)\n", + " i += 1\n", + " hidden_outputs = hidden_outputs.stack()\n", + " if training:\n", + " hidden_outputs = tf.nn.dropout(hidden_outputs, 0.5)\n", + " return hidden_outputs\n", + "\n", + "\n", + "def model(inputs, lower_cell, upper_cell, relu_layer, batch_size, training):\n", + " \"\"\"RNNColorbot model.\n", + " \n", + " The model consists of two RNN layers (made by lower_cell and upper_cell),\n", + " followed by a fully connected layer with ReLU activation.\n", + " \n", + " Args:\n", + " inputs: A tuple (chars, length)\n", + " lower_cell: An object of type tf.contrib.rnn.LSTMBlockCell\n", + " upper_cell: An object of type tf.contrib.rnn.LSTMBlockCell\n", + " relu_layer: An object of type tf.layers.Dense\n", + " batch_size: Int, the batch size to use\n", + " training: Boolean, whether the layer is used for training\n", + " \n", + " Returns:\n", + " A Tensor of shape (batch_size, 3) - the model predictions.\n", + " \"\"\"\n", + " (chars, length) = inputs\n", + " chars_time_major = tf.transpose(chars, [1, 0, 2])\n", + " chars_time_major.set_shape((None, batch_size, 256))\n", + "\n", + " hidden_outputs = rnn_layer(chars_time_major, lower_cell, batch_size, training)\n", + " final_outputs = rnn_layer(hidden_outputs, upper_cell, batch_size, training)\n", + "\n", + " # Grab just the end-of-sequence from each output.\n", + " indices = tf.stack([length - 1, range(batch_size)], axis=1)\n", + " sequence_ends = tf.gather_nd(final_outputs, indices)\n", + " return relu_layer(sequence_ends)\n", + "\n", + "def loss_fn(labels, predictions):\n", + " return tf.reduce_mean((predictions - labels) ** 2)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "JjK4gXFvFsf4", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "The train and test functions are also similar to the ones used in the Eager notebook. Since the network requires a fixed batch size, we'll train in a single shot, rather than by epoch." + ] + }, + { + "metadata": { + "id": "ZWQMExk0S6X6", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "def train(optimizer, train_data, lower_cell, upper_cell, relu_layer, batch_size, num_steps):\n", + " iterator = train_data.make_one_shot_iterator()\n", + " step = 0\n", + " while step < num_steps:\n", + " labels, chars, sequence_length = iterator.get_next()\n", + " predictions = model((chars, sequence_length), lower_cell, upper_cell, relu_layer, batch_size, training=True)\n", + " loss = loss_fn(labels, predictions)\n", + " optimizer.minimize(loss)\n", + " if step % (num_steps // 10) == 0:\n", + " print('Step', step, 'train loss', loss)\n", + " step += 1\n", + " return step\n", + "\n", + "\n", + "def test(eval_data, lower_cell, upper_cell, relu_layer, batch_size, num_steps):\n", + " total_loss = 0.0\n", + " iterator = eval_data.make_one_shot_iterator()\n", + " step = 0\n", + " while step < num_steps:\n", + " labels, chars, sequence_length = iterator.get_next()\n", + " predictions = model((chars, sequence_length), lower_cell, upper_cell, relu_layer, batch_size, training=False)\n", + " total_loss += loss_fn(labels, predictions)\n", + " step += 1\n", + " print('Test loss', total_loss)\n", + " return total_loss\n", + "\n", + "\n", + "def train_model(train_data, eval_data, batch_size, lower_cell, upper_cell, relu_layer, train_steps):\n", + " optimizer = tf.train.AdamOptimizer(learning_rate=0.01)\n", + "\n", + " train(optimizer, train_data, lower_cell, upper_cell, relu_layer, batch_size, num_steps=tf.constant(train_steps))\n", + " test(eval_data, lower_cell, upper_cell, relu_layer, 50, num_steps=tf.constant(2))\n", + "\n", + " print('Colorbot is ready to generate colors!\\n\\n')\n", + " \n", + " # In graph mode, every op needs to be a dependent of another op.\n", + " # Here, we create a no_op that will drive the execution of all other code in\n", + " # this function. Autograph will add the necessary control dependencies.\n", + " return tf.no_op()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "iopcs5hXG2od", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Finally, we add code to run inference on a single input, which we'll read from the input.\n", + "\n", + "Note the `do_not_convert` annotation that lets us disable conversion for certain functions and run them as a `py_func` instead, so you can still call them from compiled code." + ] + }, + { + "metadata": { + "id": "DyU0wnnAFEYj", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + } + } + }, + "cell_type": "code", + "source": [ + "@autograph.do_not_convert(run_as=autograph.RunMode.PY_FUNC)\n", + "def draw_prediction(color_name, pred):\n", + " pred = pred * 255\n", + " pred = pred.astype(np.uint8)\n", + " plt.axis('off')\n", + " plt.imshow(pred)\n", + " plt.title(color_name)\n", + " plt.show()\n", + "\n", + "\n", + "def inference(color_name, lower_cell, upper_cell, relu_layer):\n", + " _, chars, sequence_length = parse(color_name)\n", + " chars = tf.expand_dims(chars, 0)\n", + " sequence_length = tf.expand_dims(sequence_length, 0)\n", + " pred = model((chars, sequence_length), lower_cell, upper_cell, relu_layer, 1, training=False)\n", + " pred = tf.minimum(pred, 1.0)\n", + " pred = tf.expand_dims(pred, 0)\n", + " draw_prediction(color_name, pred)\n", + " # Create an op that will drive the entire function.\n", + " return tf.no_op()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "Nt0Kv5OCHip0", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Finally, we put everything together.\n", + "\n", + "Note that the entire training and testing code is all compiled into a single op (`tf_train_model`) that you only execute once! We also still use a `sess.run` loop for the inference part, because that requires keyboard input." + ] + }, + { + "metadata": { + "id": "-GmWa0GtYWdh", + "colab_type": "code", + "colab": { + "autoexec": { + "startup": false, + "wait_interval": 0 + }, + "output_extras": [ + { + "item_id": 12 + }, + { + "item_id": 13 + }, + { + "item_id": 14 + }, + { + "item_id": 15 + }, + { + "item_id": 16 + }, + { + "item_id": 17 + }, + { + "item_id": 18 + }, + { + "item_id": 19 + }, + { + "item_id": 20 + }, + { + "item_id": 21 + }, + { + "item_id": 23 + }, + { + "item_id": 24 + }, + { + "item_id": 25 + }, + { + "item_id": 26 + }, + { + "item_id": 27 + }, + { + "item_id": 28 + }, + { + "item_id": 29 + }, + { + "item_id": 30 + }, + { + "item_id": 31 + }, + { + "item_id": 32 + }, + { + "item_id": 33 + }, + { + "item_id": 34 + }, + { + "item_id": 35 + } + ], + "base_uri": "https://localhost:8080/", + "height": 668 + }, + "outputId": "61f4af1d-c81e-44db-9079-1a7b8ed8ce58", + "executionInfo": { + "status": "ok", + "timestamp": 1522345877153, + "user_tz": 240, + "elapsed": 75500, + "user": { + "displayName": "Dan Moldovan", + "photoUrl": "//lh5.googleusercontent.com/-Rneh8xjecyk/AAAAAAAAAAI/AAAAAAAACB4/c5vwsJpbktY/s50-c-k-no/photo.jpg", + "userId": "112023154726779574577" + } + } + }, + "cell_type": "code", + "source": [ + "def run_input_loop(sess, inference_ops, color_name_placeholder):\n", + " \"\"\"Helper function that reads from input and calls the inference ops in a loop.\"\"\"\n", + "\n", + " tb = widgets.TabBar([\"RNN Colorbot\"])\n", + " while True:\n", + " with tb.output_to(0):\n", + " try:\n", + " color_name = six.moves.input(\"Give me a color name (or press 'enter' to exit): \")\n", + " except (EOFError, KeyboardInterrupt):\n", + " break\n", + " if not color_name:\n", + " break\n", + " with tb.output_to(0):\n", + " tb.clear_tab()\n", + " sess.run(inference_ops, {color_name_placeholder: color_name})\n", + " plt.show()\n", + "\n", + "with tf.Graph().as_default():\n", + " # Read the data.\n", + " batch_size = 64\n", + " train_data = load_dataset(data_dir, train_url, batch_size)\n", + " eval_data = load_dataset(data_dir, test_url, 50, training=False)\n", + " \n", + " # Create the model components.\n", + " lower_cell, upper_cell, relu_layer = model_components()\n", + " # Create the helper placeholder for inference.\n", + " color_name_placeholder = tf.placeholder(tf.string, shape=())\n", + " \n", + " # Compile the train / test code.\n", + " tf_train_model = autograph.to_graph(train_model)\n", + " train_model_ops = tf_train_model(\n", + " train_data, eval_data, batch_size, lower_cell, upper_cell, relu_layer, train_steps=100)\n", + " \n", + " # Compile the inference code.\n", + " tf_inference = autograph.to_graph(inference)\n", + " inference_ops = tf_inference(color_name_placeholder, lower_cell, upper_cell, relu_layer)\n", + " \n", + " with tf.Session() as sess:\n", + " sess.run(tf.global_variables_initializer())\n", + " \n", + " # Run training and testing.\n", + " sess.run(train_model_ops)\n", + " \n", + " # Run the inference loop.\n", + " run_input_loop(sess, inference_ops, color_name_placeholder)" + ], + "execution_count": 22, + "outputs": [ + { + "output_type": "stream", + "text": [ + "('Successfully downloaded', 'train.csv', 28010L, 'bytes.')\n", + "('Successfully downloaded', 'test.csv', 2414L, 'bytes.')\n", + "Step 0 train loss 0.37890616\n", + "Step 10 train loss 0.18515904\n", + "Step 20 train loss 0.0892782\n", + "Step 30 train loss 0.07883155\n", + "Step 40 train loss 0.08585831\n", + "Step 50 train loss 0.09302989\n", + "Step 60 train loss 0.089012615\n", + "Step 70 train loss 0.07275697\n", + "Step 80 train loss 0.06644974\n", + "Step 90 train loss 0.0854013\n", + "Test loss 0.13216865Colorbot is ready to generate colors!\n", + "\n", + "\n", + "\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "" + ] + }, + "metadata": { + "tags": [ + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "" + ] + }, + "metadata": { + "tags": [ + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "
" + ] + }, + "metadata": { + "tags": [ + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b102d936-3379-11e8-ac70-0242ac110002\"] = colab_lib.createTabBar({\"contentBorder\": [\"0px\"], \"borderColor\": [\"#a7a7a7\"], \"tabNames\": [\"RNN Colorbot\"], \"initialSelection\": 0, \"location\": \"top\", \"contentHeight\": [\"initial\"], \"elementId\": \"id1\"});\n", + "//# sourceURL=js_e223a56194" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b103532a-3379-11e8-ac70-0242ac110002\"] = window[\"id1\"].setSelectedTabIndex(0);\n", + "//# sourceURL=js_b8c6a821fb" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b105b28c-3379-11e8-ac70-0242ac110002\"] = google.colab.output.getActiveOutputArea();\n", + "//# sourceURL=js_44805e254b" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b106197a-3379-11e8-ac70-0242ac110002\"] = document.querySelector(\"#id1_content_0\");\n", + "//# sourceURL=js_a63d3c6c47" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b1069f44-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"b106197a-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_7e203b8bce" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"b1070f38-3379-11e8-ac70-0242ac110002\"] = window[\"id1\"].setSelectedTabIndex(0);\n", + "//# sourceURL=js_d53293d4a7" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c6d90d5c-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"b105b28c-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_3000dc2c05" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c6da872c-3379-11e8-ac70-0242ac110002\"] = google.colab.output.getActiveOutputArea();\n", + "//# sourceURL=js_4136f669a3" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c6dac868-3379-11e8-ac70-0242ac110002\"] = document.querySelector(\"#id1_content_0\");\n", + "//# sourceURL=js_2f70dd9aee" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c6db07d8-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"c6dac868-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_7226726048" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c6dcc6fe-3379-11e8-ac70-0242ac110002\"] = window[\"id1\"].setSelectedTabIndex(0);\n", + "//# sourceURL=js_72e7709865" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAVQAAAFZCAYAAADHDNdrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAB9JJREFUeJzt3E1Lle0ax+HTF4jeEAyMBhE0DawI\nwsCH0AIlaGBWNJBo0CDoA0TQhmDXuKAGDioiCA2KlEAlnl05FD9Co8BeaGCQoBDa2jPZsXt4Bvu/\n0+o4Rmvd1zW4rsmP84bFamo0Go0C4H/WvNYHAPhVCCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKDy\nUxgeHq5Dhw7V4OBgPXz4sHp7e+vWrVt15cqVOnnyZN2/f78ajUbdvn27+vr6qqenp65du1YrKytV\nVfXhw4e6cOFC9fX1VV9fX01PT1dV1dzcXHV3d9eDBw/q+PHj9ccff9TExMRaXpWfWOtaHwD+zuvX\nr+vOnTs1MTFRbW1tdf78+dW16enpGh8fr/b29hobG6upqal6/Phxbdy4sS5evFgjIyM1NDRUly5d\nqv3799fw8HC9efOmTp8+XVNTU1VV9enTp2pubq5nz57V5ORk3bhxo44dO7ZW1+UnZkJl3Zudna2D\nBw9WR0dHbdiwoQYHB1fX9u7dW+3t7VVV9fLlyxocHKytW7dWa2trnTp1qp4/f16Li4s1MzNT586d\nq6qqXbt21YEDB1an1OXl5Tpx4kRVVe3Zs6fevXv3Yy/IL8OEyrr3+fPnamtrW/2+ffv21c//+Xxh\nYaHu3r1bjx49qqqqlZWVam9vr4WFhWo0GnXmzJnVvYuLi9XV1VVVVS0tLbVp06aqqmpubq6vX7/+\nX+/Dr0tQWfe2bNlSi4uLq98/fvz43X0dHR3V29tbQ0ND3zxfXl6ulpaWevLkSW3evPmbtbm5ufyB\n+W155Wfd6+zsrJmZmZqfn68vX77U2NjYd/cdOXKkxsfHa2lpqaqqRkdH6+nTp9Xa2lqHDx+u0dHR\nqqpaWlqqy5cv1/v373/YHfg9CCrrXmdnZw0MDNTAwECdPXu2enp6vrvv6NGj1dPTUwMDA9Xf318v\nXryo7u7uqqq6evVqzc7OVn9/fw0MDNTOnTtrx44dP/Ia/Aaa/B8qP4NGo1FNTU1VVfXq1au6efPm\nX06qsFZMqKx78/Pz1dXVVW/fvq1Go1GTk5O1b9++tT4W/BcTKj+FkZGRunfvXjU1NdXu3bvr+vXr\ntW3btrU+FnxDUAFCvPIDhAgqQMi6+WH/kX8eXesjAPytf/3jz79cM6EChAgqQIigAoQIKkCIoAKE\nCCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQI\nKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgq\nQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpA\niKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCI\noAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIig\nAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAC\nhAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKE\nCCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQI\nKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgq\nQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpA\niKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCI\noAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIig\nAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAC\nhAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKE\nCCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQI\nKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgq\nQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpA\niKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkCIoAKECCpAiKAChAgqQIigAoQIKkBI\nU6PRaKz1IQB+BSZUgBBBBQgRVIAQQQUIEVSAEEEFCBFUgBBBBQgRVIAQQQUIEVSAEEEFCBFUgBBB\nBQgRVIAQQQUIEVSAEEEFCBFUgBBBBQgRVIAQQQUIEVSAkH8D1Aj8lNhhe7QAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1", + "user_output" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c70592aa-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"c6da872c-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_25c3aaf79a" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c70842c0-3379-11e8-ac70-0242ac110002\"] = google.colab.output.getActiveOutputArea();\n", + "//# sourceURL=js_984c56b816" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c708dec4-3379-11e8-ac70-0242ac110002\"] = document.querySelector(\"#id1_content_0\");\n", + "//# sourceURL=js_e0451a1217" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c7092726-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"c708dec4-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_7aa23d7385" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c7099044-3379-11e8-ac70-0242ac110002\"] = window[\"id1\"].setSelectedTabIndex(0);\n", + "//# sourceURL=js_5722756ddb" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + }, + { + "output_type": "stream", + "text": [ + "Give me a color name (or press 'enter' to exit): \n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "application/javascript": [ + "window[\"c7baac12-3379-11e8-ac70-0242ac110002\"] = google.colab.output.setActiveOutputArea(window[\"c70842c0-3379-11e8-ac70-0242ac110002\"]);\n", + "//# sourceURL=js_cdd622e58f" + ], + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [ + "id1_content_0", + "outputarea_id1" + ] + } + } + ] + }, + { + "metadata": { + "id": "AHJ2c47U-A5W", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# Where do we go next?\n", + "\n", + "Autograph is available in tensorflow.contrib, but it's still in its early stages. We're excited about the possibilities it brings — write your machine learning code in the flexible Eager style, but still enjoy all the benefits that come with running in graph mode. A beta version will be available soon -- stay tuned!" + ] + } + ] +} \ No newline at end of file -- GitLab From ebba4f0dfdd4a1a42eba4a59d32222532beec031 Mon Sep 17 00:00:00 2001 From: ImSheridan Date: Fri, 30 Mar 2018 04:26:58 +0800 Subject: [PATCH 431/906] Fix math equation format in tf.contrib.bayesflow.monte_carlo (#18089) * Fix math equation format in contrib\bayesflow * Fix minor pylint error --- .../bayesflow/python/ops/monte_carlo_impl.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tensorflow/contrib/bayesflow/python/ops/monte_carlo_impl.py b/tensorflow/contrib/bayesflow/python/ops/monte_carlo_impl.py index 985177e897..d193a8459d 100644 --- a/tensorflow/contrib/bayesflow/python/ops/monte_carlo_impl.py +++ b/tensorflow/contrib/bayesflow/python/ops/monte_carlo_impl.py @@ -44,14 +44,14 @@ def expectation_importance_sampler(f, n=None, seed=None, name='expectation_importance_sampler'): - r"""Monte Carlo estimate of `E_p[f(Z)] = E_q[f(Z) p(Z) / q(Z)]`. + r"""Monte Carlo estimate of `\\(E_p[f(Z)] = E_q[f(Z) p(Z) / q(Z)]\\)`. - With `p(z) := exp{log_p(z)}`, this `Op` returns + With `\\(p(z) := exp^{log_p(z)}\\)`, this `Op` returns ``` - n^{-1} sum_{i=1}^n [ f(z_i) p(z_i) / q(z_i) ], z_i ~ q, - \approx E_q[ f(Z) p(Z) / q(Z) ] - = E_p[f(Z)] + \\(n^{-1} sum_{i=1}^n [ f(z_i) p(z_i) / q(z_i) ], z_i ~ q,\\) + \\(\approx E_q[ f(Z) p(Z) / q(Z) ]\\) + \\(= E_p[f(Z)]\\) ``` This integral is done in log-space with max-subtraction to better handle the @@ -95,9 +95,9 @@ def expectation_importance_sampler(f, log_values = log_f_z + log_p_z - q_log_prob_z return _logspace_mean(log_values) - # With f_plus(z) = max(0, f(z)), f_minus(z) = max(0, -f(z)), - # E_p[f(Z)] = E_p[f_plus(Z)] - E_p[f_minus(Z)] - # = E_p[f_plus(Z) + 1] - E_p[f_minus(Z) + 1] + # With \\(f_{plus}(z) = max(0, f(z)), f_{minus}(z) = max(0, -f(z))\\), + # \\(E_p[f(Z)] = E_p[f_{plus}(Z)] - E_p[f_{minus}(Z)]\\) + # \\( = E_p[f_{plus}(Z) + 1] - E_p[f_{minus}(Z) + 1]\\) # Without incurring bias, 1 is added to each to prevent zeros in logspace. # The logarithm is approximately linear around 1 + epsilon, so this is good # for small values of 'z' as well. @@ -121,13 +121,13 @@ def expectation_importance_sampler_logspace( name='expectation_importance_sampler_logspace'): r"""Importance sampling with a positive function, in log-space. - With `p(z) := exp{log_p(z)}`, and `f(z) = exp{log_f(z)}`, this `Op` - returns + With `\\(p(z) := exp^{log_p(z)}\\)`, and `\\(f(z) = exp{log_f(z)}\\)`, + this `Op` returns ``` - Log[ n^{-1} sum_{i=1}^n [ f(z_i) p(z_i) / q(z_i) ] ], z_i ~ q, - \approx Log[ E_q[ f(Z) p(Z) / q(Z) ] ] - = Log[E_p[f(Z)]] + \\(Log[ n^{-1} sum_{i=1}^n [ f(z_i) p(z_i) / q(z_i) ] ], z_i ~ q,\\) + \\(\approx Log[ E_q[ f(Z) p(Z) / q(Z) ] ]\\) + \\(= Log[E_p[f(Z)]]\\) ``` This integral is done in log-space with max-subtraction to better handle the @@ -196,12 +196,12 @@ def _logspace_mean(log_values): def expectation(f, samples, log_prob=None, use_reparametrization=True, axis=0, keep_dims=False, name=None): - """Computes the Monte-Carlo approximation of `E_p[f(X)]`. + """Computes the Monte-Carlo approximation of `\\(E_p[f(X)]\\)`. This function computes the Monte-Carlo approximation of an expectation, i.e., ```none - E_p[f(X)] approx= m**-1 sum_i^m f(x_j), x_j ~iid p(X) + \\(E_p[f(X)] \approx= m^{-1} sum_i^m f(x_j), x_j\ ~iid\ p(X)\\) ``` where: @@ -216,8 +216,8 @@ def expectation(f, samples, log_prob=None, use_reparametrization=True, parameterless distribution (e.g., `Normal(Y; m, s) <=> Y = sX + m, X ~ Normal(0,1)`), we can swap gradient and expectation, i.e., - `grad[ Avg{ s_i : i=1...n } ] = Avg{ grad[s_i] : i=1...n }` where - `S_n = Avg{s_i}` and `s_i = f(x_i), x_i ~ p`. + `grad[ Avg{ \\(s_i : i=1...n\\) } ] = Avg{ grad[\\(s_i\\)] : i=1...n }` where + `S_n = Avg{\\(s_i\\)}` and `\\(s_i = f(x_i), x_i ~ p\\)`. However, if p is not reparameterized, TensorFlow's gradient will be incorrect since the chain-rule stops at samples of non-reparameterized distributions. @@ -296,7 +296,8 @@ def expectation(f, samples, log_prob=None, use_reparametrization=True, Args: f: Python callable which can return `f(samples)`. samples: `Tensor` of samples used to form the Monte-Carlo approximation of - `E_p[f(X)]`. A batch of samples should be indexed by `axis` dimensions. + `\\(E_p[f(X)]\\)`. A batch of samples should be indexed by `axis` + dimensions. log_prob: Python callable which can return `log_prob(samples)`. Must correspond to the natural-logarithm of the pdf/pmf of each sample. Only required/used if `use_reparametrization=False`. @@ -316,7 +317,7 @@ def expectation(f, samples, log_prob=None, use_reparametrization=True, Returns: approx_expectation: `Tensor` corresponding to the Monte-Carlo approximation - of `E_p[f(X)]`. + of `\\(E_p[f(X)]\\)`. Raises: ValueError: if `f` is not a Python `callable`. -- GitLab From 622c0416bd6a00f9baf53e54a65ad5e5d3b87e30 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 13:24:38 -0700 Subject: [PATCH 432/906] Updating a test in constant_folding_test.cc that uses a graph with placeholder nodes by providing input to those nodes. This will allow evaluation of the fetch nodes in the optimized and original graph and check whether the output tensors produced by them are the same. PiperOrigin-RevId: 190976595 --- .../optimizers/constant_folding_test.cc | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tensorflow/core/grappler/optimizers/constant_folding_test.cc b/tensorflow/core/grappler/optimizers/constant_folding_test.cc index e0ff9b17b1..16a19ba8ce 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding_test.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding_test.cc @@ -82,6 +82,14 @@ class ConstantFoldingTest : public GrapplerTest { } }; +template +Tensor GetRandomTensor(const TensorShape& shape) { + typedef typename EnumToDataType::Type T; + Tensor tensor(DTYPE, shape); + tensor.flat() = tensor.flat().random(); + return tensor; +} + TEST_F(ConstantFoldingTest, SimpleFolding) { // Build a simple graph with a few trivially prunable ops. tensorflow::Scope s = tensorflow::Scope::NewRootScope(); @@ -371,6 +379,23 @@ TEST_F(ConstantFoldingTest, NeutralElement) { EXPECT_EQ(2, t.tensor_shape().dim(1).size()); } } + auto a_t = GetRandomTensor(TensorShape({3, 2})); + auto b_t = GetRandomTensor(TensorShape({2, 3})); + auto x_t = GetRandomTensor(TensorShape({2, 2})); + auto y_t = GetRandomTensor(TensorShape({2, 2})); + auto bias_t = GetRandomTensor(TensorShape({2})); + + auto tensors_expected = EvaluateNodes( + item.graph, item.fetch, + {{"x", x_t}, {"y", y_t}, {"a", a_t}, {"b", b_t}, {"bias", bias_t}}); + EXPECT_EQ(item.fetch.size(), tensors_expected.size()); + auto tensors = EvaluateNodes( + output, item.fetch, + {{"x", x_t}, {"y", y_t}, {"a", a_t}, {"b", b_t}, {"bias", bias_t}}); + EXPECT_EQ(item.fetch.size(), tensors.size()); + for (int i = 0; i < item.fetch.size(); ++i) { + test::ExpectTensorNear(tensors_expected[i], tensors[i], 1e-6); + } } } -- GitLab From 405efdd47c20919e5a05c86b0ae2e6c8c150e534 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 13:27:01 -0700 Subject: [PATCH 433/906] Use GraphProperties directly in ArithmeticOptimizer. PiperOrigin-RevId: 190976918 --- .../optimizers/arithmetic_optimizer.cc | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 5dd0b6f4b0..36b26c18f9 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -196,8 +196,6 @@ void SetSourceDataType(DataType dtype, NodeDef* node) { bool IsNumberType(DataType dtype) { return kNumberTypes.Contains(dtype); } -const char kOutputShapesAttr[] = "_output_shapes"; - // Shape is symbolically defined if it has a known rank, and each dimension is // defined, or is an unknown symbol (dim.size <= -2). bool ShapeIsSymbolicallyDefined(const TensorShapeProto& shape) { @@ -234,16 +232,20 @@ bool ShapesSymbolicallyEqual(const OpInfo::TensorProperties& left, // Returns whether `reshape` is an identity op. The tensor that `reshape` // reshapes is the `output_pos`-th output of node `input`. bool ReshapeIsIdentity(const NodeDef& reshape, const NodeDef& input, - const int output_pos) { - if (!reshape.attr().count(kOutputShapesAttr) || - !input.attr().count(kOutputShapesAttr)) { + const int output_pos, + const GraphProperties& graph_properties) { + const std::vector& reshape_props = + graph_properties.GetOutputProperties(reshape.name()); + const std::vector& input_props = + graph_properties.GetOutputProperties(input.name()); + if (reshape_props.empty() || input_props.empty() || + input_props.size() <= output_pos) { return false; } - PartialTensorShape src_shape( - input.attr().at(kOutputShapesAttr).list().shape(output_pos)); - PartialTensorShape dst_shape( - reshape.attr().at(kOutputShapesAttr).list().shape(0)); + const PartialTensorShape& src_shape = input_props[output_pos].shape(); + const PartialTensorShape& dst_shape = reshape_props[0].shape(); + if (src_shape.unknown_rank() || dst_shape.unknown_rank()) { return false; } @@ -256,7 +258,8 @@ bool ReshapeIsIdentity(const NodeDef& reshape, const NodeDef& input, // sizes. auto num_unknown_dim_sizes = [](const PartialTensorShape& partial_shape) { auto dim_sizes = partial_shape.dim_sizes(); - return std::count(dim_sizes.begin(), dim_sizes.end(), -1); + return std::count_if(dim_sizes.begin(), dim_sizes.end(), + [](int dim) { return dim < 0; }); }; int src_num_unknown_dim_sizes = num_unknown_dim_sizes(src_shape); int dst_num_unknown_dim_sizes = num_unknown_dim_sizes(dst_shape); @@ -1272,7 +1275,7 @@ string ArithmeticOptimizer::TrySimplifyAndReplaceUses( // outputs tensors of shape [M, N] while feeding it with tensors of shape // [M*N] (or worse). The reshape nodes are then necessary to update the // tensor metadata to the required shape. - if (ReshapeIsIdentity(*reshape, *input, output_pos)) { + if (ReshapeIsIdentity(*reshape, *input, output_pos, *graph_properties_)) { return reshape->input(0); } } @@ -1695,18 +1698,11 @@ Status ArithmeticOptimizer::Optimize(Cluster* /*cluster*/, // Shapes are only needed in aggressive mode. graph_properties_.reset(new GraphProperties(item)); TF_RETURN_IF_ERROR(graph_properties_->InferStatically(false)); - // TODO(ezhulenev): Use GraphProperties to lookup tensor shapes directly - TF_RETURN_IF_ERROR(graph_properties_->AnnotateOutputShapes(optimized_graph_)); // Perform the optimizations. DedupComputations(); TF_RETURN_IF_ERROR(SimplifyArithmeticOps()); - // Clear output shapes. - for (int i = 0; i < optimized_graph->node_size(); ++i) { - optimized_graph_->mutable_node(i)->mutable_attr()->erase(kOutputShapesAttr); - } - return Status::OK(); } -- GitLab From 0390fbec15f3d99c3badce3d666893ff124f7846 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Thu, 29 Mar 2018 13:28:05 -0700 Subject: [PATCH 434/906] Docs: Move TFLite docs into tensorflow.org PiperOrigin-RevId: 190977057 --- tensorflow/contrib/lite/README.md | 240 +----------------- .../lite/g3doc/TFLite-Architecture.jpg | Bin 48710 -> 0 bytes tensorflow/docs_src/mobile/leftnav_files | 1 + .../docs_src/mobile/tflite/demo_android.md | 156 ++++++++++-- tensorflow/docs_src/mobile/tflite/demo_ios.md | 2 +- tensorflow/docs_src/mobile/tflite/devguide.md | 224 ++++++++++++++++ tensorflow/docs_src/mobile/tflite/index.md | 4 +- 7 files changed, 363 insertions(+), 264 deletions(-) delete mode 100644 tensorflow/contrib/lite/g3doc/TFLite-Architecture.jpg create mode 100644 tensorflow/docs_src/mobile/tflite/devguide.md diff --git a/tensorflow/contrib/lite/README.md b/tensorflow/contrib/lite/README.md index c15ae3f233..a676b705f1 100644 --- a/tensorflow/contrib/lite/README.md +++ b/tensorflow/contrib/lite/README.md @@ -1,238 +1,8 @@ # TensorFlow Lite -TensorFlow Lite is TensorFlow's lightweight solution for mobile and embedded devices. It enables low-latency inference of on-device machine learning models with a small binary size and fast performance supporting hardware acceleration. -TensorFlow Lite uses many techniques for achieving low latency like optimizing the kernels for specific mobile apps, pre-fused activations, quantized kernels that allow smaller and faster (fixed-point math) models, and in the future, leverage specialized machine learning hardware to get the best possible performance for a particular model on a particular device. +TensorFlow Lite is TensorFlow's lightweight solution for mobile and embedded +devices. It enables low-latency inference of on-device machine learning models +with a small binary size and fast performance supporting hardware acceleration. -![image](g3doc/TFLite-Architecture.jpg) -# Getting Started with an Android Demo App - -This section contains an example application using TensorFlow Lite for Android devices. The demo is a sample camera app that classifies images continuously using either a quantized Mobilenet model or a floating point Inception-v3 model. A device running Android 5.0 ( API 21) or higher is required to run the demo. - -There are 3 ways to get the demo app to your device - - Download the prebuilt binary or - - Use Android Studio to build the application or - - Download the source code for TensorFlow Lite and the demo and build it using bazel - -## Description -In the demo app, inference is done using the TensorFlow Lite Java API. The demo app classifies frames in real-time, displaying the top most probable classifications. It also displays the time taken to detect the object. - -## Downloading the pre-built binary -The fastest path to trying the demo, is to download the pre-built binary -[TfLiteCameraDemo.apk](https://storage.googleapis.com/download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk) - -Once the apk is installed, click the app icon to start the app. The first-time the app is opened, the app asks for runtime permissions to access the device camera. The demo app opens the back-camera of the device and recognizes the objects in the camera's field of view. At the bottom of the image (or at the left of the image if the device is in landscape mode), it shows the latency of classification and the top three objects classified. - -## Building in Android Studio using TensorFlow Lite AAR from JCenter -The simplest way to compile the demo app, and try out changes to the project code is to use AndroidStudio. - - - Install the latest version of Android Studio 3 as specified [here](https://developer.android.com/studio/index.html). - - Make sure the Android SDK version is greater than 26 and NDK version is greater than 14 (in the Android Studio Settings). - - Import the `tensorflow/contrib/lite/java/demo` directory as a new Android Studio project. - - Click through installing all the Gradle extensions it requests. - - Either - - Download the quantized Mobilenet TensorFlow Lite model from [here](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip) - - unzip and copy mobilenet_quant_v1_224.tflite to the assets directory: - `tensorflow/contrib/lite/java/demo/app/src/main/assets/` - - Or download the floating point Inception-v3 model from [here](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) - - unzip and copy inceptionv3_non_slim_2015.tflite to the assets directory - - change the chosen classifier in [Camera2BasicFragment.java](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java) from - `classifier = new ImageClassifierQuantizedMobileNet(getActivity());` - to - `classifier = new ImageClassifierFloatInception(getActivity());` - - Build and run the demo app - -## Building TensorFlow Lite and the demo app from source - -### Clone the TensorFlow repo -- git clone - [https://github.com/tensorflow/tensorflow](https://github.com/tensorflow/tensorflow) - -### Install Bazel -If bazel is not installed on your system, install it now by following [these directions](https://bazel.build/versions/master/docs/install.html) - -NOTE: Bazel does not fully support building Android on Windows yet. Full support for Gradle/CMake builds is coming soon, but in the meantime Windows users should download the [prebuilt binary](https://storage.googleapis.com/download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk) instead. - -### Install Android NDK and SDK -Bazel is the primary build system for TensorFlow. Bazel and the Android NDK and SDK must be installed on your system. - - Install the latest version of Bazel as per the instructions on the [Bazel website](https://bazel.build/versions/master/docs/install.html) - - The Android NDK is required to build the native (C/C++) TensorFlow Lite code. The current recommended version is 14b, which can be found [here](https://developer.android.com/ndk/downloads/older_releases.html#ndk-14b-downloads). - - The Android SDK and build tools may be obtained [here](https://developer.android.com/tools/revisions/build-tools.html), or alternatively as part of [Android Studio](https://developer.android.com/studio/index.html). Build tools API >= 23 is required to build the TF Android demo (though it will run on API >= 21 devices). - - In the root of the TensorFlow repository update the `WORKSPACE` file with the `api_level` and location of the SDK and NDK. If you installed it with AndroidStudio the SDK path can be found in the SDK manager, and the default NDK path is:`{SDK path}/ndk-bundle.` - -``` -android_sdk_repository ( - name = "androidsdk", - api_level = 23, - build_tools_version = "23.0.2", - path = "/home/xxxx/android-sdk-linux/", -) - -android_ndk_repository( - name = "androidndk", - path = "/home/xxxx/android-ndk-r10e/", - api_level = 19, -) -``` - -Additional details on building with Android can be found [here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/README.md). - -### Build the source code -Run bazel with the following command to build the demo. - -Build the demo app: - -``` -bazel build --cxxopt=--std=c++11 //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo -``` - -### Note - -Currently, we only support building the Android demo app within a Python 2 -environment (due to a Bazel bug). - -### More about the demo -The demo is resizing each camera image frame to (224 width * 224 height) to match the quantized Mobilenet model being used (299 * 299 for Inception-v3). The resized image is converted into a ByteBuffer row by row of size 1 * 224 * 224 * 3 bytes, where 1 is the number of images in a batch. 224 * 224 (299 * 299) is the width and height of the image. 3 bytes represents three colors of a pixel. This demo uses the TensorFlow Lite Java inference API for models which take a single input and provide a single output. This outputs a two-dimensional array, with the first dimension being the category index and the second dimension being the confidence of classification. Both models have 1001 unique categories and the app sorts the probabilities of all the categories and displays the top three. The model file must be downloaded and bundled within the assets directory of the app. - -# iOS Demo App - -Similar to the Android demo app, there's an iOS camera app that uses exactly the same model (224 * 224 quantized Mobilenet). - -This demo app requires a camera so it doesn't work with simulators. It need to be executed on a real iOS device. Follow the instructions to build and run the demo app: - -1. Run `tensorflow/contrib/lite/examples/ios/download_models.sh` to download the model files used by the demo app. -1. Install [CocoaPods](https://cocoapods.org/) if it wasn't installed yet: `sudo gem install cocoapods`. -1. Run `pod install` in `tensorflow/contrib/lite/examples/ios/camera` to generate the workspace file. -1. Open the project by running `open tflite_camera_example.xcworkspace`, and build the app in XCode. - -# TensorFlow Lite Quick Start - -## Step 1. Decide which GraphDef to use - Depending on the use case, the developer may choose to use one of the popular - open-sourced models such as InceptionV3 or MobileNets, re-train these models - with their own custom data set or even build their own custom model. - -### Using a pre-trained model - -[MobileNets](https://research.googleblog.com/2017/06/mobilenets-open-source-models-for.html) is a family of mobile-first computer vision models for [TensorFlow](https://www.tensorflow.org/) designed to effectively maximize accuracy while being mindful of the restricted resources for an on-device or embedded application. MobileNets are small, low-latency, low-power models parameterized to meet the resource constraints of a variety of use cases. They can be built upon for classification, detection, embeddings and segmentation similar to how other popular large scale models, such as [Inception](https://arxiv.org/pdf/1602.07261.pdf), are used. Google provides 16 pre-trained [ImageNet](http://www.image-net.org/challenges/LSVRC/) classification checkpoints for MobileNets for use in mobile projects of all sizes. - -[Inception-v3](https://arxiv.org/abs/1512.00567) is an image recognition model which achieves fairly high accuracy in recognizing general objects with 1000 classes, like "Zebra", "Dalmatian", and "Dishwasher". The model extracts general features from input images using a convolutional neural network and classifies them based on those features with fully-connected and softmax layers. - -[On Device Smart Reply](https://research.googleblog.com/2017/02/on-device-machine-intelligence.html) is an on-device model which provides one-touch replies for an incoming text message by suggesting contextually relevant messages. The model is built specifically for memory constrained devices such as watches & phones and it has been successfully used to surface [Smart Replies on Android Wear](https://research.googleblog.com/2017/02/on-device-machine-intelligence.html). Note that this model only works on Android as of now. - -These pre-trained models can be downloaded from [here](g3doc/models.md). - -### Retrain Inception-V3 or MobileNet for a custom data set -The above pre-trained models have been trained on the ImageNet data set, which consists of 1000 predefined classes. A model will need to be re-trained if these classes are not relevant or useful for a given use case. This technique is called transfer learning, which starts with a model that has been already trained on a problem and will then be retrained on a similar problem. Deep learning from scratch can take days, but transfer learning can be done fairly quickly. In order to do this, a developer will need to generate their custom data set labeled with the relevant classes. - -The [TensorFlow for Poets](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets/) codelab walks through this process step-by-step. The retraining code supports retraining for both floating point and quantized inference. - -# Getting started with RaspberryPi - -Using RaspberryPi can be accomplished by following the [Makefile instructions](g3doc/rpi.md). That will give a you a static library (.a) that you can build your app against. Python bindings will be coming soon as well as a demo app. - -### Train a custom model -A developer may choose to train a custom model using Tensorflow. TensorFlow documentation has [several tutorials](https://www.tensorflow.org/tutorials/) for building and training models. If the user has written a model using TensorFlow's Slim Framework the first step is to export this to a GraphDef file. This is necessary because Slim does not store the model structure outside the code, so to communicate with other parts of the framework it needs to be exported. Documentation for the export can be found [here](https://github.com/tensorflow/models/tree/master/research/slim#Export). The output of this step will be a .pb file for the custom model. - -TensorFlow Lite currently supports a subset of TensorFlow operators. Please refer to [this document](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md) for details of supported operators and their usage. This -set will continue to expand in future releases of Tensorflow Lite. - - -## Step 2. Model format conversion - -The model generated in Step 1 is a standard Tensorflow model. After the completion of Step 1 a user should have a standard .pb or .pbtxt GraphDef file. If the application developer is using a pre-trained model (as defined in Step 1 above), they can download a ready to use, already converted model for use from [here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/models.md). Models generated using retraining (aka transfer learning) or custom models will need to be converted using the steps mentioned below. - -A prerequisite to converting the model to the Tensorflow Lite format is to freeze the graph. - -Since we employ several formats, the following definitions may be useful: - - GraphDef (.pb) - a protobuf that represents the TensorFlow training and or computation graph. This contains operators, tensors, and variables definitions. - - - CheckPoint (.ckpt) - Serialized variables from a TensorFlow graph. Note, this does not contain the graph structure, so alone it cannot typically be interpreted. - - - FrozenGraphDef - a subclass of GraphDef that contains no variables. A GraphDef can be converted to a frozen graphdef by taking a checkpoint and a graphdef and converting every variable into a constant with the value looked up in the checkpoint. - - - SavedModel - A collection of GraphDef and CheckPoint together with a signature that labels input and output arguments to a model. A GraphDef and Checkpoint can be extracted from a saved model. - - - TensorFlow lite model (.tflite) - a serialized flatbuffer, containing TensorFlow lite operators and Tensors for the TensorFlow lite interpreter. This is most analogous to TensorFlow frozen GraphDefs. - -### Freeze Graph -To use this .pb GraphDef file within TensorFlow Lite, the application developer will need checkpoints containing trained weight parameters. The .pb contains only the structure of the graph. The process of merging the checkpoint values with the graph structure is known as "freezing" the graph. - -The developer should know where the checkpoints folder is present or checkpoints can also be downloaded for a pre-trained model (Example: Here is a link to the [MobileNets](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md)). - -Graph freezing can be done using the command below (and modifying the arguments appropriately) - -``` -bazel build tensorflow/python/tools:freeze_graph - -bazel-bin/tensorflow/python/tools/freeze_graph\ - --input_graph=/tmp/mobilenet_v1_224.pb \ - --input_checkpoint=/tmp/checkpoints/mobilenet-10202.ckpt \ - --input_binary=true --output_graph=/tmp/frozen_mobilenet_v1_224.pb \ - --output_node_names=MobilenetV1/Predictions/Reshape_1 -``` - -The user has to first build the freeze_graph script using bazel and then run the script. The input_binary flag has to be enabled to ensure that the protobuf is read and written in binary format. The user has to input the .pb and the .ckpt files to freeze the graph The output_node_names may not be obvious outside of the code that built the model. The easiest way to find them is to visualize the graph, either with -graphviz, or [in tensorboard](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets-2/#3). - -This frozen Graphdef is now ready to be converted to flatbuffer format (.tflite) for use on Android or iOS. On Android users have the flexibility to use either the float or quantized versions of the frozen graphdef, if available, using the Tensorflow Optimizing Converter tool. - -Here is a sample command line to convert the frozen Graphdef to '.tflite' format for The Tensorflow Optimizing Converter supports both float and quantized models, however, different configuration parameters are needed depending on whether a FLOAT or QUANTIZED mode is being used. -(Here is a link to the pb [file](https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_1.0_224_frozen.tgz)). - -``` -bazel build tensorflow/contrib/lite/toco:toco - -bazel-bin/tensorflow/contrib/lite/toco/toco \ - --input_file=$(pwd)/mobilenet_v1_1.0_224/frozen_graph.pb \ - --input_format=TENSORFLOW_GRAPHDEF --output_format=TFLITE \ - --output_file=/tmp/mobilenet_v1_1.0_224.tflite --inference_type=FLOAT \ - --input_type=FLOAT --input_arrays=input \ - --output_arrays=MobilenetV1/Predictions/Reshape_1 --input_shapes=1,224,224,3 -``` - -- The input_file argument should point to the frozen GraphDef file that holds the model architecture. -- The output_file argument should point to where the TensorFlow Lite model file should be generated. -- The input_type and inference_type arguments should be set to FLOAT, unless converted a [quantized](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/) model. -- Setting the input_array, output_array and input_shape arguments are a bit trickier. The easiest way to find these values is to explore the graph in tensorboard . The user should reuse the arguments that were used for specifying the output nodes for inference in the `freeze_graph`step. - -Note, it is also possible to use the Tensorflow Optimizing Converter through protos either from Python or from the command line see the -documentation [here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/toco/python/toco_from_protos.py). A developer can then integrate the conversion step into their model design workflow to ensure that a model will be easily convertible to a mobile inference graph. For example, - -```python -import tensorflow as tf - -img = tf.placeholder(name="img", dtype=tf.float32, shape=(1, 64, 64, 3)) -val = img + tf.constant([1., 2., 3.]) + tf.constant([1., 4., 4.]) -out = tf.identity(val, name="out") -with tf.Session() as sess: - tflite_model = tf.contrib.lite.toco_convert(sess.graph_def, [img], [out]) - open("converteds_model.tflite", "wb").write(tflite_model) - -``` -For detailed instructions on how to use the Tensorflow Optimizing Converter, please see [here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md). - -You may refer to the [Ops compatibility guide](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md) for troubleshooting help. If that doesn't help, please file an [issue](https://github.com/tensorflow/tensorflow/issues). - -If you would like to see a visual description of your TensorFlow Lite model after conversion, you can use tensorflow/contrib/lite/tools/visualize.py by running -```sh -bazel run tensorflow/contrib/lite/tools:visualize -- model.tflite model_viz.html -``` -and then visualize the resulting HTML file in a browser. - -## Step 3. Use the TensorFlow Lite model for inference in a mobile app - -After completion of Step 2 the developer should have a .tflite model. - -### For Android -Because Android apps need to be written in Java, and core TensorFlow is in C++, a JNI library is provided to interface between the two. Its interface is aimed only at inference, so it provides the ability to load a graph, set up inputs, and run the model to calculate particular outputs. The full documentation for the set of methods can be seen [here](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/). The demo app is also open sourced on [github](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/app). - -The [demo app](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/app) uses this interface, so it's a good place to look for example usage. You can also download the prebuilt binary [here](http://download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk). - -Note that you'd need to follow instructions for installing TensorFlow on Android, setting up bazel and Android Studio outlined [here](https://www.tensorflow.org/mobile/android_build). - -### For iOS -Follow the documentation [here](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/ios.md) to get integrate a TFLite model into your app. - -## Core ML support - -Core ML is a machine learning framework used across Apple products. In addition to using Tensorflow Lite models directly in their applications, developers have the option to convert their trained Tensorflow models to the [CoreML](https://developer.apple.com/machine-learning/) format for use on Apple devices. For information on how to use the converter please refer to the [Tensorflow-CoreML converter documentation](https://github.com/tf-coreml/tf-coreml). +See the documentation: https://www.tensorflow.org/mobile/tflite/ +Documentation edits can be made here: [tensorflow/docs_src/mobile/tflite](../../docs_src/mobile/tflite) diff --git a/tensorflow/contrib/lite/g3doc/TFLite-Architecture.jpg b/tensorflow/contrib/lite/g3doc/TFLite-Architecture.jpg deleted file mode 100644 index bc83946647c6a923a8a0bd3a041b42e4febe6a31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48710 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 PEh0SG0d`57{QsK(>> diff --git a/tensorflow/docs_src/mobile/tflite/demo_android.md b/tensorflow/docs_src/mobile/tflite/demo_android.md index c94b5597a6..7f2f8882a2 100644 --- a/tensorflow/docs_src/mobile/tflite/demo_android.md +++ b/tensorflow/docs_src/mobile/tflite/demo_android.md @@ -1,42 +1,144 @@ -# TensorFlow Lite Demo for Android +# Android Demo App -The TensorFlow Lite demo is a camera app that continuously classifies whatever -it sees from your device's back camera, using a quantized MobileNet model. +An example Android application using TensorFLow Lite is available +[on GitHub](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/app). +The demo is a sample camera app that classifies images continuously +using either a quantized Mobilenet model or a floating point Inception-v3 model. +To run the demo, a device running Android 5.0 ( API 21) or higher is required. -You'll need an Android device running Android 5.0 or higher to run the demo. +In the demo app, inference is done using the TensorFlow Lite Java API. The demo +app classifies frames in real-time, displaying the top most probable +classifications. It also displays the time taken to detect the object. -To get you started working with TensorFlow Lite on Android, we'll walk you -through building and deploying our TensorFlow demo app in Android Studio. +There are three ways to get the demo app to your device: -Note: For a more detailed guide see the -[TFLite Codelab](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets-2-tflite/index.html#0) +* Download the [prebuilt binary APK](http://download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk). +* Use Android Studio to build the application. +* Download the source code for TensorFlow Lite and the demo and build it using + bazel. -It's also possible to build the demo app with Bazel, but we only recommend -this for advanced users who are very familiar with the Bazel build -environment. For more information on that, see our page [on Github](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite#building-tensorflow-lite-and-the-demo-app-from-source). -## Build and deploy with Android Studio +## Download the pre-built binary -1. Clone the TensorFlow repository from GitHub if you haven't already: +The easiest way to try the demo is to download the +[pre-built binary APK](https://storage.googleapis.com/download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk) - git clone https://github.com/tensorflow/tensorflow +Once the APK is installed, click the app icon to start the program. The first +time the app is opened, it asks for runtime permissions to access the device +camera. The demo app opens the back-camera of the device and recognizes objects +in the camera's field of view. At the bottom of the image (or at the left +of the image if the device is in landscape mode), it displays top three objects +classified and the classification latency. -2. Install the latest version of Android Studio from [here](https://developer.android.com/studio/index.html). -3. From the **Welcome to Android Studio** screen, use the **Import Project - (Gradle, Eclipse ADT, etc)** option to import the - `tensorflow/contrib/lite/java/demo` directory as an existing Android Studio - Project. +## Build in Android Studio with TensorFlow Lite AAR from JCenter - Android Studio may prompt you to install Gradle upgrades and other tool - versions; you should accept these upgrades. +Use Android Studio to try out changes in the project code and compile the demo +app: -4. Download the TensorFlow Lite MobileNet model from [here](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip). +* Install the latest version of + [Android Studio](https://developer.android.com/studio/index.html). +* Make sure the Android SDK version is greater than 26 and NDK version is greater + than 14 (in the Android Studio settings). +* Import the `tensorflow/contrib/lite/java/demo` directory as a new + Android Studio project. +* Install all the Gradle extensions it requests. - Unzip this and copy the `mobilenet_quant_v1_224.tflite` file to the assets - directory: `tensorflow/contrib/lite/java/demo/app/src/main/assets/` +To get a model, either: -5. Build and run the app in Android Studio. +* Download the quantized [Mobilenet TensorFlow Lite model](https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip) + and unzip and copy `mobilenet_quant_v1_224.tflite` to the assets directory: + `tensorflow/contrib/lite/java/demo/app/src/main/assets/`. +* Or, download the floating point [Inception-v3 model](https://storage.googleapis.com/download.tensorflow.org/models/tflite/inception_v3_slim_2016_android_2017_11_10.zip) + and unzip and copy `inceptionv3_non_slim_2015.tflite` to the assets + directory. Change the chosen classifier in + [Camera2BasicFragment.java](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/Camera2BasicFragment.java)
+ from: `classifier = new ImageClassifierQuantizedMobileNet(getActivity());`
+ to: `classifier = new ImageClassifierFloatInception(getActivity());`. -You'll have to grant permissions for the app to use the device's camera. Point -the camera at various objects and enjoy seeing how the model classifies things! +Now you can build and run the demo app. + + +## Build TensorFlow Lite and the demo app from source + +### Clone the TensorFlow repo + +```sh +git clone https://github.com/tensorflow/tensorflow +``` + +### Install Bazel + +If `bazel` is not installed on your system, see +[Installing Bazel](https://bazel.build/versions/master/docs/install.html). + +Note: Bazel does not currently support Android builds on Windows. Windows users +should download the +[prebuilt binary](https://storage.googleapis.com/download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk). + +### Install Android NDK and SDK + +The Android NDK is required to build the native (C/C++) TensorFlow Lite code. The +current recommended version is *14b* and can be found on the +[NDK Archives](https://developer.android.com/ndk/downloads/older_releases.html#ndk-14b-downloads) +page. + +The Android SDK and build tools can be +[downloaded separately](https://developer.android.com/tools/revisions/build-tools.html) +or used as part of +[Android Studio](https://developer.android.com/studio/index.html). To build the +TensorFlow Lite Android demo, build tools require API >= 23 (but it will run on +devices with API >= 21). + +In the root of the TensorFlow repository, update the `WORKSPACE` file with the +`api_level` and location of the SDK and NDK. If you installed it with +Android Studio, the SDK path can be found in the SDK manager. The default NDK +path is:`{SDK path}/ndk-bundle.` For example: + +``` +android_sdk_repository ( + name = "androidsdk", + api_level = 23, + build_tools_version = "23.0.2", + path = "/home/xxxx/android-sdk-linux/", +) + +android_ndk_repository( + name = "androidndk", + path = "/home/xxxx/android-ndk-r10e/", + api_level = 19, +) +``` + +Some additional details are available on the +[TF Lite Android App page](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/README.md). + +### Build the source code + +To build the demo app, run `bazel`: + +``` +bazel build --cxxopt=--std=c++11 //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo +``` + +Caution: Because of an bazel bug, we only support building the Android demo app +within a Python 2 environment. + + +## About the demo + +The demo app is resizing each camera image frame (224 width * 224 height) to +match the quantized MobileNets model (299 * 299 for Inception-v3). The resized +image is converted—row by row—into a +[ByteBuffer](https://developer.android.com/reference/java/nio/ByteBuffer.html). +Its size is 1 * 224 * 224 * 3 bytes, where 1 is the number of images in a batch. +224 * 224 (299 * 299) is the width and height of the image. 3 bytes represents +the 3 colors of a pixel. + +This demo uses the TensorFlow Lite Java inference API +for models which take a single input and provide a single output. This outputs a +two-dimensional array, with the first dimension being the category index and the +second dimension being the confidence of classification. Both models have 1001 +unique categories and the app sorts the probabilities of all the categories and +displays the top three. The model file must be downloaded and bundled within the +assets directory of the app. diff --git a/tensorflow/docs_src/mobile/tflite/demo_ios.md b/tensorflow/docs_src/mobile/tflite/demo_ios.md index 3ee9b1cbca..3be21da89f 100644 --- a/tensorflow/docs_src/mobile/tflite/demo_ios.md +++ b/tensorflow/docs_src/mobile/tflite/demo_ios.md @@ -1,4 +1,4 @@ -# TensorFlow Lite Demo for iOS +# iOS Demo App The TensorFlow Lite demo is a camera app that continuously classifies whatever it sees from your device's back camera, using a quantized MobileNet model. These diff --git a/tensorflow/docs_src/mobile/tflite/devguide.md b/tensorflow/docs_src/mobile/tflite/devguide.md new file mode 100644 index 0000000000..5b521dca7b --- /dev/null +++ b/tensorflow/docs_src/mobile/tflite/devguide.md @@ -0,0 +1,224 @@ +# Developer Guide + +Using a TensorFlow Lite model in your mobile app requires multiple +considerations: you must choose a pre-trained or custom model, convert the model +to a TensorFLow Lite format, and finally, integrate the model in your app. + +## 1. Choose a model + +Depending on the use case, you can choose one of the popular open-sourced models, +such as *InceptionV3* or *MobileNets*, and re-train these models with a custom +data set or even build your own custom model. + +### Use a pre-trained model + +[MobileNets](https://research.googleblog.com/2017/06/mobilenets-open-source-models-for.html) +is a family of mobile-first computer vision models for TensorFlow designed to +effectively maximize accuracy, while taking into consideration the restricted +resources for on-device or embedded applications. MobileNets are small, +low-latency, low-power models parameterized to meet the resource constraints for +a variety of uses. They can be used for classification, detection, embeddings, and +segmentation—similar to other popular large scale models, such as +[Inception](https://arxiv.org/pdf/1602.07261.pdf). Google provides 16 pre-trained +[ImageNet](http://www.image-net.org/challenges/LSVRC/) classification checkpoints +for MobileNets that can be used in mobile projects of all sizes. + +[Inception-v3](https://arxiv.org/abs/1512.00567) is an image recognition model +that achieves fairly high accuracy recognizing general objects with 1000 classes, +for example, "Zebra", "Dalmatian", and "Dishwasher". The model extracts general +features from input images using a convolutional neural network and classifies +them based on those features with fully-connected and softmax layers. + +[On Device Smart Reply](https://research.googleblog.com/2017/02/on-device-machine-intelligence.html) +is an on-device model that provides one-touch replies for incoming text messages +by suggesting contextually relevant messages. The model is built specifically for +memory constrained devices, such as watches and phones, and has been successfully +used in Smart Replies on Android Wear. Currently, this model is Android-specific. + +These pre-trained models are [available for download](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/g3doc/models.md) + +### Re-train Inception-V3 or MobileNet for a custom data set + +These pre-trained models were trained on the *ImageNet* data set which contains +1000 predefined classes. If these classes are not sufficient for your use case, +the model will need to be re-trained. This technique is called +*transfer learning* and starts with a model that has been already trained on a +problem, then retrains the model on a similar problem. Deep learning from +scratch can take days, but transfer learning is fairly quick. In order to do +this, you need to generate a custom data set labeled with the relevant classes. + +The [TensorFlow for Poets](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets/) +codelab walks through the re-training process step-by-step. The code supports +both floating point and quantized inference. + +### Train a custom model + +A developer may choose to train a custom model using Tensorflow (see the +@{$tutorials} for examples of building and training models). If you have already +written a model, the first step is to export this to a @{tf.GraphDef} file. This +is required because some formats do not store the model structure outside the +code, and we must communicate with other parts of the framework. See +[Exporting the Inference Graph](https://github.com/tensorflow/models/blob/master/research/slim/README.md) +to create .pb file for the custom model. + +TensorFlow Lite currently supports a subset of TensorFlow operators. Refer to the +[TensorFlow Lite & TensorFlow Compatibility Guide](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md) +for supported operators and their usage. This set of operators will continue to +grow in future Tensorflow Lite releases. + + +## 2. Convert the model format + +The model generated (or downloaded) in the previous step is a *standard* +Tensorflow model and you should now have a .pb or .pbtxt @{tf.GraphDef} file. +Models generated with transfer learning (re-training) or custom models must be +converted—but, we must first freeze the graph to convert the model to the +Tensorflow Lite format. This process uses several model formats: + +* @{tf.GraphDef} (.pb) —A protobuf that represents the TensorFlow training or + computation graph. It contains operators, tensors, and variables definitions. +* *CheckPoint* (.ckpt) —Serialized variables from a TensorFlow graph. Since this + does not contain a graph structure, it cannot be interpreted by itself. +* `FrozenGraphDef` —A subclass of `GraphDef` that does not contain + variables. A `GraphDef` can be converted to a `FrozenGraphDef` by taking a + CheckPoint and a `GraphDef`, and converting each variable into a constant + using the value retrieved from the CheckPoint. +* `SavedModel` —A `GraphDef` and CheckPoint with a signature that labels + input and output arguments to a model. A `GraphDef` and CheckPoint can be + extracted from a `SavedModel`. +* *TensorFlow Lite model* (.tflite) —A serialized + [FlatBuffer](https://google.github.io/flatbuffers/) that contains TensorFlow + Lite operators and tensors for the TensorFlow Lite interpreter, similiar to a + `FrozenGraphDef`. + +### Freeze Graph + +To use the `GraphDef` .pb file with TensorFlow Lite, you must have checkpoints +that contain trained weight parameters. The .pb file only contains the structure +of the graph. The process of merging the checkpoint values with the graph +structure is called *freezing the graph*. + +You should have a checkpoints folder or download them for a pre-trained model +(for example, +[MobileNets](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md)). + +To freeze the graph, use the following command (changing the arguments): + +``` +freeze_graph --input_graph=/tmp/mobilenet_v1_224.pb \ + --input_checkpoint=/tmp/checkpoints/mobilenet-10202.ckpt \ + --input_binary=true \ + --output_graph=/tmp/frozen_mobilenet_v1_224.pb \ + --output_node_names=MobileNetV1/Predictions/Reshape_1 +``` + +The `input_binary` flag must be enabled so the protobuf is read and written in +a binary format. Set the `input_graph` and `input_checkpoint` files. + +The `output_node_names` may not be obvious outside of the code that built the +model. The easiest way to find them is to visualize the graph, either with +[TensorBoard](https://codelabs.developers.google.com/codelabs/tensorflow-for-poets-2/#3) +or `graphviz`. + +The frozen `GraphDef` is now ready for conversion to the `FlatBuffer` format +(.tflite) for use on Android or iOS devices. For Android, the Tensorflow +Optimizing Converter tool supports both float and quantized models. To convert +the frozen `GraphDef` to the .tflite format: + +``` +toco --input_file=$(pwd)/mobilenet_v1_1.0_224/frozen_graph.pb \ + --input_format=TENSORFLOW_GRAPHDEF \ + --output_format=TFLITE \ + --output_file=/tmp/mobilenet_v1_1.0_224.tflite \ + --inference_type=FLOAT \ + --input_type=FLOAT \ + --input_arrays=input \ + --output_arrays=MobilenetV1/Predictions/Reshape_1 \ + --input_shapes=1,224,224,3 +``` + +The `input_file` argument should reference the frozen `GraphDef` file +containing the model architecture. The [frozen_graph.pb](https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_1.0_224_frozen.tgz) +file used here is available for download. `output_file` is where the TensorFlow +Lite model will get generated. The `input_type` and `inference_type` +arguments should be set to `FLOAT`, unless converting a +@{$performance/quantization$quantized model}. Setting the `input_array`, +`output_array`, and `input_shape` arguments are not as straightforward. The +easiest way to find these values is to explore the graph using Tensorboard. Reuse +the arguments for specifying the output nodes for inference in the +`freeze_graph` step. + +It is also possible to use the Tensorflow Optimizing Converter with protobufs +from either Python or from the command line (see the +[toco_from_protos.py](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/toco/python/toco_from_protos.py) +example). This allows you to integrate the conversion step into the model design +workflow, ensuring the model is easily convertible to a mobile inference graph. +For example: + +```python +import tensorflow as tf + +img = tf.placeholder(name="img", dtype=tf.float32, shape=(1, 64, 64, 3)) +val = img + tf.constant([1., 2., 3.]) + tf.constant([1., 4., 4.]) +out = tf.identity(val, name="out") + +with tf.Session() as sess: + tflite_model = tf.contrib.lite.toco_convert(sess.graph_def, [img], [out]) + open("converteds_model.tflite", "wb").write(tflite_model) +``` + +For usage, see the Tensorflow Optimizing Converter +[command-line examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md). + +Refer to the +[Ops compatibility guide](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md) +for troubleshooting help, and if that doesn't help, please +[file an issue](https://github.com/tensorflow/tensorflow/issues). + +The [development repo](https://github.com/tensorflow/tensorflow) contains a tool +to visualize TensorFlow Lite models after conversion. To build the +[visualize.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/tools/visualize.py) +tool: + +```sh +bazel run tensorflow/contrib/lite/tools:visualize -- model.tflite model_viz.html +``` + +This generates an interactive HTML page listing subgraphs, operations, and a +graph visualization. + + +## 3. Use the TensorFlow Lite model for inference in a mobile app + +After completing the prior steps, you should now have a .tflite model file. + +### Android + +Since Android apps are written in Java and the core TensorFlow library is in C++, +a JNI library is provided as an interface. This is only meant for inference—it +provides the ability to load a graph, set up inputs, and run the model to +calculate outputs. + +The open source Android demo app uses the JNI interface and is available +[on GitHub](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo/app). +You can also download a +[prebuilt APK](http://download.tensorflow.org/deps/tflite/TfLiteCameraDemo.apk). +See the @{$tflite/demo_android} guide for details. + +The @{$mobile/android_build} guide has instructions for installing TensorFlow on +Android and setting up `bazel` and Android Studio. + +### iOS + +To integrate a TensorFlow model in an iOS app, see the +[TensorFlow Lite for iOS](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/g3doc/ios.md) +guide and @{$tflite/demo_ios} guide. + +#### Core ML support + +Core ML is a machine learning framework used in Apple products. In addition to +using Tensorflow Lite models directly in your applications, you can convert +trained Tensorflow models to the +[CoreML](https://developer.apple.com/machine-learning/) format for use on Apple +devices. To use the converter, refer to the +[Tensorflow-CoreML converter documentation](https://github.com/tf-coreml/tf-coreml). diff --git a/tensorflow/docs_src/mobile/tflite/index.md b/tensorflow/docs_src/mobile/tflite/index.md index beb24794fc..11f11ea4dc 100644 --- a/tensorflow/docs_src/mobile/tflite/index.md +++ b/tensorflow/docs_src/mobile/tflite/index.md @@ -155,7 +155,9 @@ retraining for both floating point and quantized inference. The following diagram shows the architectural design of TensorFlow Lite: -![tensorflow lite architecture](https://www.tensorflow.org/images/tflite-architecture.jpg) +TensorFlow Lite architecture diagram Starting with a trained TensorFlow model on disk, you'll convert that model to the TensorFlow Lite file format (`.tflite`) using the TensorFlow Lite -- GitLab From 9d1d379bcdd19d496fd8d2659c21a5510e045c5a Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Thu, 29 Mar 2018 13:31:23 -0700 Subject: [PATCH 435/906] Docs: Add Eager Execution guide to Programmer's Guide. PiperOrigin-RevId: 190977505 --- tensorflow/contrib/eager/README.md | 20 +- .../contrib/eager/python/g3doc/guide.md | 898 +--------------- .../docs_src/programmers_guide/eager.md | 992 ++++++++++++++++++ .../docs_src/programmers_guide/leftnav_files | 3 +- 4 files changed, 1015 insertions(+), 898 deletions(-) create mode 100644 tensorflow/docs_src/programmers_guide/eager.md diff --git a/tensorflow/contrib/eager/README.md b/tensorflow/contrib/eager/README.md index 9d2ca07c3a..9a3b780af8 100644 --- a/tensorflow/contrib/eager/README.md +++ b/tensorflow/contrib/eager/README.md @@ -1,12 +1,8 @@ # Eager Execution -> *WARNING*: This is a preview/pre-alpha version. The API and performance -> characteristics are subject to change. - -Eager execution is an experimental interface to TensorFlow that provides an -imperative programming style (à la [NumPy](http://www.numpy.org)). When you -enable eager execution, TensorFlow operations execute immediately; you do not -execute a pre-constructed graph with +Eager execution provides an imperative interface to TensorFlow (similiar to +[NumPy](http://www.numpy.org)). When you enable eager execution, TensorFlow +operations execute immediately; you do not execute a pre-constructed graph with [`Session.run()`](https://www.tensorflow.org/api_docs/python/tf/Session). For example, consider a simple computation in TensorFlow: @@ -33,7 +29,7 @@ print(m) ## Caveats This feature is in early stages and work remains to be done in terms of smooth -support for distributed and multi-GPU training and CPU performance. +support for distributed and multi-GPU training and performance. - [Known issues](https://github.com/tensorflow/tensorflow/issues?q=is%3Aissue%20is%3Aopen%20label%3Acomp%3Aeager) - Feedback is welcome, please consider @@ -41,21 +37,23 @@ support for distributed and multi-GPU training and CPU performance. ## Installation -Eager execution is included in TensorFlow versions 1.5 and above. +Eager execution is included in TensorFlow versions 1.7 and above. Installation instructions at https://www.tensorflow.org/install/ ## Documentation For an introduction to eager execution in TensorFlow, see: -- [User Guide](python/g3doc/guide.md) +- [User Guide](https://www.tensorflow.org/programmers_guide/eager) ([source](../../docs_src/programmers_guide/eager.md)) - Notebook: [Basic Usage](python/examples/notebooks/1_basics.ipynb) - Notebook: [Gradients](python/examples/notebooks/2_gradients.ipynb) - Notebook: [Importing Data](python/examples/notebooks/3_datasets.ipynb) ## Changelog -- 2017/10/31: Initial preview release. +- 2017/10/31: Initial preview release (in TensorFlow 1.5) - 2017/12/01: Example of dynamic neural network: [SPINN: Stack-augmented Parser-Interpreter Neural Network](https://arxiv.org/abs/1603.06021). See [README.md](python/examples/spinn/README.md) for details. +- 2017/03: Core functionality moved out of the experimental tf.contrib namespace + in TensorFlow 1.7. diff --git a/tensorflow/contrib/eager/python/g3doc/guide.md b/tensorflow/contrib/eager/python/g3doc/guide.md index 11064981c6..2d2aba6908 100644 --- a/tensorflow/contrib/eager/python/g3doc/guide.md +++ b/tensorflow/contrib/eager/python/g3doc/guide.md @@ -1,892 +1,18 @@ -# TensorFlow Eager Execution - -## What is this? +# Eager execution Eager execution is a feature that makes TensorFlow execute operations -immediately: concrete values are returned, instead of a computational graph to -be executed later. - -As a result, enabling eager execution provides: - -- A [NumPy](http://www.numpy.org/)-like library for numerical computation with - support for GPU acceleration and automatic differentiation. -- A flexible platform for machine learning research and experimentation. - -Eager execution is under active development. This guide walks through an -alpha/preview release. In particular, not all TensorFlow APIs currently work -with eager execution enabled, and some models may be slow to execute, compared -to models defined without using eager execution. - -## Installation - -Eager execution is included in TensorFlow versions 1.5 and above. -Installation instructions at https://www.tensorflow.org/install/ - -The contents of this guide are compatible with TensorFlow 1.5. However, if you -run into bugs that are fixed in source but not the release, you may want to -either [build from source](https://www.tensorflow.org/install/install_sources) -or try a nightly build. The nightly builds are available as: - -- [`pip` packages](https://github.com/tensorflow/tensorflow/blob/master/README.md#installation) and - -- [docker](https://hub.docker.com/r/tensorflow/tensorflow/) images. - -For example, to run the latest nightly docker image: - -```sh -# If you have a GPU, use https://github.com/NVIDIA/nvidia-docker -docker pull tensorflow/tensorflow:nightly-gpu -docker run --runtime=nvidia -it -p 8888:8888 tensorflow/tensorflow:nightly-gpu - -# If you do not have a GPU, use the CPU-only image -docker pull tensorflow/tensorflow:nightly -docker run -it -p 8888:8888 tensorflow/tensorflow:nightly -``` - -And then visit http://localhost:8888 in your browser for a Jupyter notebook -environment. - -## Getting Started - -With TensorFlow installed, eager execution is enabled via a single call: - -```python -import tensorflow as tf - -import tensorflow.contrib.eager as tfe - -tfe.enable_eager_execution() -``` - -Enabling eager execution changes how TensorFlow functions behave (in particular, -`Tensor` objects will reference concrete values instead of being symbolic -handles to nodes in a computational graph). As a result, eager execution should -be enabled at the beginning of a program and cannot be disabled afterwards in -the same program. - -Code examples in the rest of this guide assume that eager execution has been -enabled. - -## A library for numerical computation - -A significant fraction of the [TensorFlow -API](https://www.tensorflow.org/api_docs/python/) consists of numerical -operations: -[arithmetic operations](https://www.tensorflow.org/api_guides/python/math_ops#Arithmetic_Operators), -[matrix operations](https://www.tensorflow.org/api_guides/python/math_ops#Matrix_Math_Functions), -[linear algebra operations](https://www.tensorflow.org/versions/master/api_docs/python/tf/linalg), -etc. - -With eager execution enabled, these operations consume and return -multi-dimensional arrays as `Tensor` objects, similar to NumPy -[`ndarray`s](https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.ndarray.html). -For example: - -```python -# Multiply two 2x2 matrices -x = tf.matmul([[1, 2], - [3, 4]], - [[4, 5], - [6, 7]]) -# Add one to each element -# (tf.add supports broadcasting) -y = tf.add(x, 1) - -# Create a random random 5x3 matrix -z = tf.random_uniform([5, 3]) - -print(x) -print(y) -print(z) -``` - -Output: - -``` -tf.Tensor( -[[16 19] - [36 43]], shape=(2, 2), dtype=int32) -tf.Tensor( -[[17 20] - [37 44]], shape=(2, 2), dtype=int32) -tf.Tensor( -[[ 0.25058532 0.0929395 0.54113817] - [ 0.3108716 0.93350542 0.84909797] - [ 0.53081679 0.12788558 0.01767385] - [ 0.29725885 0.33540785 0.83588314] - [ 0.38877153 0.39720535 0.78914213]], shape=(5, 3), dtype=float32) -``` - -For convenience, these operations can also be triggered via operator overloading -of the `Tensor` object. For example, the `+` operator is equivalent to `tf.add`, -`-` to `tf.subtract`, `*` to `tf.multiply`, etc.: - -```python -x = (tf.ones([1], dtype=tf.float32) + 1) * 2 - 1 -print(x) -``` - -Output: - -``` -tf.Tensor([ 3.], shape=(1,), dtype=float32) -``` - -### Converting to and from NumPy - -The operations above automatically convert Python objects (like lists of -numbers) and NumPy arrays to `Tensor` objects. `Tensor` objects can also be used -as NumPy arrays by numpy operations. - -```python -import numpy as np - -x = tf.add(1, 1) # tf.Tensor with a value of 2 -y = tf.add(np.array(1), np.array(1)) # tf.Tensor with a value of 2 -z = np.multiply(x, y) # numpy.int64 with a value of 4 -``` - -Alternatively, they can be explicitly converted using -[`tf.constant`](https://www.tensorflow.org/api_docs/python/tf/constant), as -shown in the next example. - -Conversely, you can call the `numpy()` method of a `Tensor` object' to obtain -its NumPy `ndarray` value. For example: - -```python -import numpy as np - -np_x = np.array(2., dtype=np.float32) -x = tf.constant(np_x) - -py_y = 3. -y = tf.constant(py_y) - -z = x + y + 1 - -print(z) -print(z.numpy()) -``` - -Output: - -``` -tf.Tensor(6.0, shape=(), dtype=float32) -6.0 -``` - -### GPU acceleration - -Many TensorFlow operations support GPU acceleration. With eager execution -enabled, [computation is *not* automatically -offloaded](https://www.tensorflow.org/tutorials/using_gpu) to GPUs. Instead, you -must explicitly specify when GPUs should be used. - -The simplest way to do this is to enclose your computation in a `with -tf.device('/gpu:0')` block. Also of interest is the `tfe.num_gpus()` function, -which returns the number of available GPUs. - -For example, consider this snippet to measure the time to multiply two 1000x1000 -matrices on CPU: - -```python -import time - -def measure(x): - # The very first time a GPU is used by TensorFlow, it is initialized. - # So exclude the first run from timing. - tf.matmul(x, x) - - start = time.time() - for i in range(10): - tf.matmul(x, x) - end = time.time() - - return "Took %s seconds to multiply a %s matrix by itself 10 times" % (end - start, x.shape) - -# Run on CPU: -with tf.device("/cpu:0"): - print("CPU: %s" % measure(tf.random_normal([1000, 1000]))) - -# If a GPU is available, run on GPU: -if tfe.num_gpus() > 0: - with tf.device("/gpu:0"): - print("GPU: %s" % measure(tf.random_normal([1000, 1000]))) -``` - -Output (exact numbers will depend on the characteristics of the hardware): - -```python -CPU: Took 0.145531892776 seconds to multiply a (1000, 1000) matrix by itself 10 times -GPU: Took 0.000458955764771 seconds to multiply a (1000, 1000) matrix by itself 10 times -``` - -Alternatively, methods on the `Tensor` object can be used to explicitly copy the -`Tensor` to a different device. Operations are typically executed on the device -on which the inputs are placed. For example: - -```python -x = tf.random_normal([10, 10]) - -x_gpu0 = x.gpu() -x_cpu = x.cpu() - -_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU -_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0 - -if tfe.num_gpus() > 1: - x_gpu1 = x.gpu(1) - _ = tf.matmul(x_gpu1, x_gpu1) # Runs on GPU:1 -``` - -### Automatic Differentiation - -[Automatic -differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation) is -very useful when implementing many machine learning algorithms (e.g., -[backpropagation](https://en.wikipedia.org/wiki/Backpropagation) for training -neural networks). For this purpose, TensorFlow eager execution provides an -[autograd](https://github.com/HIPS/autograd)-style API for automatic -differentiation. Specifically, the functions: - -- `tfe.gradients_function(f)`: Returns a Python function that computes the - derivatives of the Python function `f` with respect to its arguments. `f` - must return a scalar value. When the returned function is invoked, it - returns a list of `Tensor` objects (one element for each argument of `f`). -- `tfe.value_and_gradients_function(f)`: Similar to `tfe.gradients_function`, - except that when the returned function is invoked, it returns the value of - `f` in addition to the list of derivatives of `f` with respect to its - arguments. - -These functions naturally apply to higher order differentiation as well. For -example: - -```python -def f(x): - return tf.multiply(x, x) # Or x * x -assert 9 == f(3.).numpy() - -df = tfe.gradients_function(f) -assert 6 == df(3.)[0].numpy() - -# Second order deriviative. -d2f = tfe.gradients_function(lambda x: df(x)[0]) -assert 2 == d2f(3.)[0].numpy() - -# Third order derivative: Will be None -d3f = tfe.gradients_function(lambda x : d2f(x)[0]) -assert None == d3f(3.)[0] -``` - -These functions can be used to train models. For example, consider the following -simple linear regression model: - -```python -def prediction(input, weight, bias): - return input * weight + bias - -# A toy dataset of points around 3 * x + 2 -NUM_EXAMPLES = 1000 -training_inputs = tf.random_normal([NUM_EXAMPLES]) -noise = tf.random_normal([NUM_EXAMPLES]) -training_outputs = training_inputs * 3 + 2 + noise - -# A loss function: Mean-squared error -def loss(weight, bias): - error = prediction(training_inputs, weight, bias) - training_outputs - return tf.reduce_mean(tf.square(error)) - -# Function that returns the derivative of loss with respect to -# weight and bias -grad = tfe.gradients_function(loss) - -# Train for 200 steps (starting from some random choice for W and B, on the same -# batch of data). -W = 5. -B = 10. -learning_rate = 0.01 -print("Initial loss: %f" % loss(W, B).numpy()) -for i in range(200): - (dW, dB) = grad(W, B) - W -= dW * learning_rate - B -= dB * learning_rate - if i % 20 == 0: - print("Loss at step %d: %f" % (i, loss(W, B).numpy())) -print("Final loss: %f" % loss(W, B).numpy()) -print("W, B = %f, %f" % (W.numpy(), B.numpy())) -``` - -Output: (the exact numbers may vary depending on the randomness in noise) - -``` -Initial loss: 66.730003 -Loss at step 0: 64.200096 -Loss at step 20: 29.872814 -Loss at step 40: 14.233772 -Loss at step 60: 7.090570 -Loss at step 80: 3.819887 -Loss at step 100: 2.318821 -Loss at step 120: 1.628385 -Loss at step 140: 1.310142 -Loss at step 160: 1.163167 -Loss at step 180: 1.095162 -Final loss: 1.064711 -W, B = 3.094944, 2.161383 -``` - -To utilize the GPU, place the code above within a `with tf.device("/gpu:0"):` -block. (However, this particular model, with only two floating point parameters, -is unlikely to benefit from GPU acceleration.) - -### Customizing gradients - -One may want to define custom gradients for an operation, or for a function. -This may be useful for multiple reasons, including providing a more efficient -or more [numerically stable](https://en.wikipedia.org/wiki/Numerical_stability) -gradient for a sequence of operations. - -For example, consider the function `log(1 + e^x)`, which commonly occurs in the -computation of cross entropy and log likelihoods. - -```python -def log1pexp(x): -  return tf.log(1 + tf.exp(x)) -grad_log1pexp = tfe.gradients_function(log1pexp) - -# Works fine at x = 0. -assert 0.5 == float(grad_log1pexp(0.)[0]) - -# Returns a `nan` at x = 100 due to numerical instability. -import math -assert math.isnan(float(grad_log1pexp(100.)[0])) -``` - -We can define a custom gradient for the above function that analytically -simplifies the gradient expression. - -```python -@tfe.custom_gradient -def log1pexp(x): -  e = tf.exp(x) -  def grad(dy): -    return dy * (1 - 1 / (1 + e)) -  return tf.log(1 + e), grad -grad_log1pexp = tfe.gradients_function(log1pexp) - -# Works as before at x = 0. -assert 0.5 == float(grad_log1pexp(0.)[0]) - -# But now works at x = 100 as well. -assert 1.0 == float(grad_log1pexp(100.)[0]) -``` -Also notice how the gradient function implementation reuses an expression -(`tf.exp(x)`) computed during the forward pass, hence making the gradient -computation more efficient by avoiding redundant computation. - -## Building and training models - -In practice, your computation may have many parameters to be optimized (by -computing derivatives). Encapsulating them into re-usable classes/objects -makes the code easier to follow than writing a single top-level function with -many arguments. - -In fact, eager execution encourages use of the [Keras](https://keras.io)-style -"Layer" classes in the -[`tf.layers`](https://www.tensorflow.org/api_docs/python/tf/layers) -module. - -Furthermore, you may want to apply more sophisticated techniques to compute -parameter updates, such as those in -[`tf.train.Optimizer`](https://www.tensorflow.org/api_guides/python/train#Optimizers) -implementations. - -This next section walks through using the same `Optimizer` and `Layer` APIs used -to build trainable TensorFlow graphs in an environment where eager execution is -enabled. - -### Variables and Optimizers - -`tfe.Variable` objects store mutable `Tensor` values that can be accessed during -training, making automatic differentiation easier. In particular, parameters of -a model can be encapsulated in Python classes as variables. - -`tfe.gradients_function(f)` introduced earlier computes the derivatives of `f` -with respect to its arguments. However, it requires all parameters of interest -to be arguments of `f`, which becomes cumbersome when `f` depends on a large -number of trainable parameters. - -`tfe.implicit_gradients` is an alternative function with some useful properties: - -- It computes the derivatives of `f` with respect to all the `tfe.Variable`s - used by `f`. -- When the returned function is invoked, it returns a list of - (gradient value, Variable object) tuples. - -Representing model parameters as `Variable` objects, along with the use of -`tfe.implicit_gradients`, typically results in better encapsulation. For -example, the linear regression model described above can be written into a -class: - -```python -class Model(object): - def __init__(self): - self.W = tfe.Variable(5., name='weight') - self.B = tfe.Variable(10., name='bias') - - def predict(self, inputs): - return inputs * self.W + self.B - - -# The loss function to be optimized -def loss(model, inputs, targets): - error = model.predict(inputs) - targets - return tf.reduce_mean(tf.square(error)) - -# A toy dataset of points around 3 * x + 2 -NUM_EXAMPLES = 1000 -training_inputs = tf.random_normal([NUM_EXAMPLES]) -noise = tf.random_normal([NUM_EXAMPLES]) -training_outputs = training_inputs * 3 + 2 + noise - -# Define: -# 1. A model -# 2. Derivatives of a loss function with respect to model parameters -# 3. A strategy for updating the variables based on the derivatives -model = Model() -grad = tfe.implicit_gradients(loss) -optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) - -# The training loop -print("Initial loss: %f" % - loss(model, training_inputs, training_outputs).numpy()) -for i in range(201): - optimizer.apply_gradients(grad(model, training_inputs, training_outputs)) - if i % 20 == 0: - print("Loss at step %d: %f" % - (i, loss(model, training_inputs, training_outputs).numpy())) -print("Final loss: %f" % loss(model, training_inputs, training_outputs).numpy()) -print("W, B = %s, %s" % (model.W.numpy(), model.B.numpy())) -``` - -Output: - -``` -Initial loss: 69.693184 -Loss at step 0: 66.987854 -Loss at step 20: 30.553387 -Loss at step 40: 14.250237 -Loss at step 60: 6.955020 -Loss at step 80: 3.690550 -Loss at step 100: 2.229739 -Loss at step 120: 1.576032 -Loss at step 140: 1.283496 -Loss at step 160: 1.152584 -Loss at step 180: 1.093999 -Final loss: 1.067780 -W, B = 3.0114281, 2.0865183 -``` - -Using `implicit_gradients` avoids the need to provide all the trainable -parameters of the model as arguments to the `loss` function. - -### Using Keras and the Layers API - -[Keras](https://keras.io) is a popular API for defining model structures. The -[`tf.keras.layers`](https://www.tensorflow.org/api_docs/python/tf/keras/layers) -module provides a set of building blocks for models and is implemented using the -`tf.layers.Layer` subclasses in the -[`tf.layers`](https://www.tensorflow.org/api_docs/python/tf/layers) -module. We encourage the use of these same building blocks when using -TensorFlow's eager execution feature. For example, the very same linear -regression model can be built using `tf.layers.Dense`: - -```python -class Model(object): - def __init__(self): - self.layer = tf.layers.Dense(1) - - def predict(self, inputs): - return self.layer(inputs) -``` - -The `tf.layers` API makes it more convenient to define more sophisticated -models. For example, the following will train an MNIST model: - -```python -class MNISTModel(object): - def __init__(self, data_format): - # 'channels_first' is typically faster on GPUs - # while 'channels_last' is typically faster on CPUs. - # See: https://www.tensorflow.org/performance/performance_guide#data_formats - if data_format == 'channels_first': - self._input_shape = [-1, 1, 28, 28] - else: - self._input_shape = [-1, 28, 28, 1] - self.conv1 = tf.layers.Conv2D(32, 5, - padding='same', - activation=tf.nn.relu, - data_format=data_format) - self.max_pool2d = tf.layers.MaxPooling2D( - (2, 2), (2, 2), padding='same', data_format=data_format) - self.conv2 = tf.layers.Conv2D(64, 5, - padding='same', - activation=tf.nn.relu, - data_format=data_format) - self.dense1 = tf.layers.Dense(1024, activation=tf.nn.relu) - self.dropout = tf.layers.Dropout(0.5) - self.dense2 = tf.layers.Dense(10) - - def predict(self, inputs): - x = tf.reshape(inputs, self._input_shape) - x = self.max_pool2d(self.conv1(x)) - x = self.max_pool2d(self.conv2(x)) - x = tf.layers.flatten(x) - x = self.dropout(self.dense1(x)) - return self.dense2(x) - -def loss(model, inputs, targets): - return tf.reduce_mean( - tf.nn.softmax_cross_entropy_with_logits( - logits=model.predict(inputs), labels=targets)) - - -# Load the training and validation data -from tensorflow.examples.tutorials.mnist import input_data -data = input_data.read_data_sets("./mnist_data", one_hot=True) - -# Train -device = "gpu:0" if tfe.num_gpus() else "cpu:0" -model = MNISTModel('channels_first' if tfe.num_gpus() else 'channels_last') -optimizer = tf.train.AdamOptimizer(learning_rate=1e-4) -grad = tfe.implicit_gradients(loss) -for i in range(20001): - with tf.device(device): - (inputs, targets) = data.train.next_batch(50) - optimizer.apply_gradients(grad(model, inputs, targets)) - if i % 100 == 0: - print("Step %d: Loss on training set : %f" % - (i, loss(model, inputs, targets).numpy())) -print("Loss on test set: %f" % loss(model, data.test.images, data.test.labels).numpy()) -``` - -For a more complete example, see [the example in the tensorflow/models -repository](https://github.com/tensorflow/models/tree/master/official/mnist/mnist_eager.py). - -### Checkpointing trained variables - -TensorFlow Variables (`tfe.Variable`) provide a way to represent shared, -persistent state of your model. The `tfe.Checkpoint` class provides a means to -save and restore variables to and from _checkpoints_. - -For example: - -```python -# Create variables. -x = tfe.Variable(10.) -y = tfe.Variable(5.) - -# Indicate that the variables should be saved as "x" and "y". -checkpoint = tfe.Checkpoint(x=x, y=y) - -# Assign new values to the variables and save. -x.assign(2.) -save_path = checkpoint.save('/tmp/ckpt') - -# Change the variable after saving. -x.assign(11.) -assert 16. == (x + y).numpy() # 11 + 5 - -# Restore the values in the checkpoint. -checkpoint.restore(save_path) # save_path='/tmp/ckpt-1' - -assert 7. == (x + y).numpy() # 2 + 5 -``` - -### `tf.keras.Model` - -You may often want to organize your models using classes, like the `MNISTModel` -class described above. We recommend inheriting from the `tf.keras.Model` class -as it provides conveniences like keeping track of all model variables. - -Sub-classes of `tf.keras.Model` may register `Layer`s (like classes in -[`tf.layers`](https://www.tensorflow.org/api_docs/python/tf/layers), or [Keras -layers](https://www.tensorflow.org/api_docs/python/tf/keras/layers)) by -assigning them to attributes (`self.name = layer_object`) and define the -computation in an implementation of `call()`. - -Note that `tf.layers.Layer` objects (like `tf.layers.Dense`) create variables -lazily, when the first input is encountered. - -For example, consider the following two-layer neural network: - -```python -class TwoLayerNet(tf.keras.Model): - def __init__(self): - super(TwoLayerNet, self).__init__() - self.layer1 = tf.layers.Dense(2, activation=tf.nn.relu, use_bias=False) - self.layer2 = tf.layers.Dense(3, use_bias=False) - - def call(self, x): - return self.layer2(self.layer1(x)) - -net = TwoLayerNet() - -# No variables created yet -assert 0 == len(net.variables) - -# They are created on first input: -inp = tf.constant([[1.]]) - -# Since input is a 1x1 matrix, net.l1 has 2 units and net.l2 has 3 units, -# the output is the product of a 1x1 matrix with a 1x2 matrix with a 2x3 -# matrix. -assert [1, 3] == net(inp).shape.as_list() # Invoke net; get output shape. -assert 1 == len(net.layer1.variables) -assert 1 == len(net.layer2.variables) -assert 2 == len(net.variables) # weights for each layer. -assert [1, 2] == net.variables[0].shape.as_list() # weights of layer1. -assert [2, 3] == net.variables[1].shape.as_list() # weights of layer2. -``` - -The `tf.keras.Model` class is itself a sub-class of `tf.layers.Layer`. This -allows instances of `tf.keras.Model` to be embedded in other models. For -example: - -```python -class ThreeLayerNet(tf.keras.Model): - def __init__(self): - super(ThreeLayerNet, self).__init__() - self.a = TwoLayerNet() - self.b = tf.layers.Dense(4, use_bias=False) - - def call(self, x): - return self.b(self.a(x)) - -net = ThreeLayerNet() - -assert [1, 4] == net(inp).shape.as_list() -assert 3 == len(net.variables) -assert [1, 2] == net.variables[0].shape.as_list() -assert [2, 3] == net.variables[1].shape.as_list() -assert [3, 4] == net.variables[2].shape.as_list() -``` - -See more examples in -[`tensorflow/contrib/eager/python/examples`](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples). - -`tfe.Checkpoint` provides a convenient way to save and load training -checkpoints. Let's define something simple to train. We set an objective for the -output of our network, choose an optimizer, and a location for the checkpoint: - -```python -objective = tf.constant([[2., 3., 4., 5.]]) -optimizer = tf.train.AdamOptimizer(0.01) -checkpoint_directory = '/tmp/tfe_example' -checkpoint_prefix = os.path.join(checkpoint_directory, 'ckpt') -net = ThreeLayerNet() -``` - -We group them in a `tfe.Checkpoint` and request that it be restored. This -ensures that variables created by these objects are restored before their values -are used. Our training loop is the same whether starting training or resuming -from a previous checkpoint: - -```python -global_step = tf.train.get_or_create_global_step() -checkpoint = tfe.Checkpoint( - global_step=global_step, optimizer=optimizer, network=net) -checkpoint.restore(tf.train.latest_checkpoint(checkpoint_directory)) -for _ in range(100): - loss_fn = lambda: tf.norm(net(inp) - objective) - optimizer.minimize(loss_fn, global_step=global_step) - if tf.equal(global_step % 20, 0): - print("Step %d, output %s" % (global_step.numpy(), - net(inp).numpy())) - # Save the checkpoint. - checkpoint.save(checkpoint_prefix) -``` - -The first time it runs, `Model` variables are initialized randomly. Then the -output is trained to match the objective we've set: - -``` -Step 20, output [[ 0.03575622 0.29863232 0.03474367 0.24735749]] -Step 40, output [[ 0.40646029 0.9856872 0.46851286 0.95358551]] -Step 60, output [[ 1.74541104 2.800704 1.79055595 2.74783421]] -Step 80, output [[ 2.14977384 3.44340849 3.96120024 5.16242075]] -Step 100, output [[ 1.99943113 3.02364397 3.93500996 4.9610076 ]] -``` - -In subsequent iterations, variables are initialized with the values read from -the latest checkpoint. Running the same code again, we continue from where we -left off: - -``` -Step 120, output [[ 1.99234128 3.0271616 3.98732996 4.96401167]] -Step 140, output [[ 2.00133467 3.01270437 4.00616646 5.00406504]] -Step 160, output [[ 1.99647415 2.9956708 3.99064088 4.99632359]] -Step 180, output [[ 2.00699997 3.00904822 4.00706148 5.01193142]] -Step 200, output [[ 1.98334622 2.98249531 3.97375059 4.97123432]] -``` - - -### Summaries, metrics and TensorBoard - -[TensorBoard](https://www.tensorflow.org/get_started/summaries_and_tensorboard) -is a popular tool for understanding, debugging and optimizing the model training -process. To benefit from the visualizations offered by TensorBoard, summary -events need to be written during the course of execution of your program. You -might find many Tensorflow programs that include the -[`tf.summary`](https://www.tensorflow.org/api_guides/python/summary) operations -during graph construction. - -`tf.summary` operations are *not* compatible with eager execution, but an -equivalent alternative exists in -[`tf.contrib.summary`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/summary) -that is compatible with both eager execution and graph construction. - -During model construction simply insert summary operations like -`tf.contrib.summary.scalar`. These operations do nothing by default, unless a -summary writer is currently active and a writing policy is set. - -For example, to record summaries once every 100 global steps, use: - -```python -tf.train.get_or_create_global_step() # Ensuring the global step variable exists -writer = tf.contrib.summary.create_file_writer(logdir) - -for _ in range(iterations): - with writer.as_default(): - with tf.contrib.summary.record_summaries_every_n_global_steps(100): - # your model code goes here - tf.contrib.summary.scalar('loss', loss) - # ... -``` - -See the full mnist example in -[`tensorflow/contrib/eager/python/examples/mnist`](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/mnist) -for a full model using `tf.contrib.summary`. - -Similarly to summaries, the metrics in `tf.metrics` are currently not compatible -with eager execution. We instead provide object-oriented metrics in the -`tfe.metrics` package, which are compatible with graph construction as well. - -Metrics in the `tfe.metrics`, such as `tfe.metrics.Mean` and -`tfe.Metrics.Accuracy`, all implement an intuitive object-oriented -interface. Here's an example of how to use the `tfe.metrics.Mean` metric: - -```python -# Metrics are objects, which can be created and destroyed. -my_mean = tfe.metrics.Mean(name='my_mean') -# While a metric is active, you can call it as a function to accumulate into its -# internal state. -my_mean(0.0) -my_mean(10.0) -# Once you've finished updating the metric, you can get its result. In this case -# a simple average over all the calls to it. If a summary writer is active the -# metric will write the appropriate summaries using the metric name. -assert 5.0 == my_mean.result().numpy() -``` - -For a full example of a model using metrics for evaluation, see the mnist -example in -[`tensorflow/contrib/eager/python/examples/mnist`](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/mnist). - -### Input Pipelines - -The discussion above has been centered around the computation executed by your -model. The -[`tf.data`](https://www.tensorflow.org/api_docs/python/tf/data) -module provides APIs to build complex input pipelines from simple, reusable -pieces. - -If you're familiar with constructing `tf.data.Dataset` objects when building -TensorFlow graphs, the same API calls are used when eager execution is enabled. -However, the process of iterating over elements of the dataset differs between -eager execution and graph construction. When eager execution is enabled, the -discussion on iterator creation using `make_one_shot_iterator()` and -`get_next()` in the -[Programmer's Guide](https://www.tensorflow.org/programmers_guide/datasets) is -*not* applicable. Instead, a more Pythonic `Iterator` class is available. - -For example: - -```python -# Create a source Dataset from in-memory numpy arrays. -# For reading from files on disk, you may want to use other Dataset classes -# like the TextLineDataset or the TFRecordDataset. -dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6]) - -# Apply transformations, shuffling, batching etc. -dataset = dataset.map(tf.square).shuffle(2).batch(2) - -# Use tfe.Iterator to iterate over the dataset. -for x in tfe.Iterator(dataset): - print(x) -``` - -Output: - -``` -tf.Tensor([4 9], shape=(2,), dtype=int32) -tf.Tensor([16 25], shape=(2,), dtype=int32) -tf.Tensor([36 1], shape=(2,), dtype=int32) -``` - -## Interoperating with Graphs - -Eager execution improves the process of model development in Python; however, -because it is in its earliest stages, it does not yet support some features -available to [TensorFlow -graphs](https://www.tensorflow.org/get_started/get_started#the_computational_graph) -that are desirable when deploying models in production. In particular, eager -execution does not yet support distributed training, exporting models (to other -[programming languages](https://www.tensorflow.org/api_docs/), [TensorFlow -serving](https://www.tensorflow.org/serving/), and mobile applications), and -various memory and computation optimizations that are applied to TensorFlow's -dataflow graphs. - -That said, the APIs used to build modes are exactly the same whether executing -eagerly or constructing graphs. This means that you can iteratively develop your -model with eager execution enabled and later, if needed, use the same code to -reap the benefits of representing models as computational graphs. - -For example, the same model definition used to construct a graph in -[mnist.py`](https://github.com/tensorflow/models/tree/master/official/mnist/mnist.py) -can be trained with eager execution enabled as in [`mnist_eager.py`](https://github.com/tensorflow/models/tree/master/official/mnist/mnist_eager.py). - -Other models in the [examples -directory](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/) -demonstrate this as well. - -Some differences worth noting: - -- There is no notion of a `tf.placeholder` or a `tf.Session` when eager - execution is enabled. -- Many properties on the `tf.Tensor` object, like `tf.Tensor.name`, - `tf.Tensor.op`, `tf.Tensor.inputs` are not meaningful when eager execution - is enabled and their use will raise an `AttributeError`. -- To use `tfe.implicit_gradients` in graph construction, variables must be - created with [`use_resource=True`] provided to - [`tf.get_variable()`](https://www.tensorflow.org/api_docs/python/tf/get_variable) - or - [`tf.variable_scope()`](https://www.tensorflow.org/api_docs/python/tf/variable_scope). -- Some API calls (such as the functional-style `tf.layers.dense`, - `tf.layers.conv2d`) are not compatible with eager execution. Use of such - methods should raise an error indicating the alternative (e.g., the - `tf.layers.Dense` and `tf.layers.Conv2D` classes). - -## What next? +immediately: concrete values are returned, instead of creating a computational +graph that is executed later. -Please give eager execution a spin. This feature is in early stages and is -evolving, so we welcome your feedback via issues on GitHub (see [known -issues](https://github.com/tensorflow/tensorflow/labels/comp:eager)). +A user guide is available: https://www.tensorflow.org/programmers_guide/eager +([source file](../../../../docs_src/programmers_guide/eager.md)) -You may want to browse through some sample code, including benchmarks for some: +We welcome feedback through [GitHub issues](https://github.com/tensorflow/tensorflow/labels/comp:eager). -- [Linear Regression](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/linear_regression) -- [MNIST handwritten digit classifier](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/mnist) -- [ResNet50 image classification](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/resnet50) -- [RNN to generate colors](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/rnn_colorbot) -- [RNN language model](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/rnn_ptb) +Sample code is available, including benchmarks for some: +- [Linear Regression](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/linear_regression) +- [MNIST handwritten digit classifier](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/mnist) +- [ResNet50 image classification](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/resnet50) +- [RNN to generate colors](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/rnn_colorbot) +- [RNN language model](https://www.tensorflow.org/code/tensorflow/contrib/eager/python/examples/rnn_ptb) diff --git a/tensorflow/docs_src/programmers_guide/eager.md b/tensorflow/docs_src/programmers_guide/eager.md new file mode 100644 index 0000000000..9ae1e602f4 --- /dev/null +++ b/tensorflow/docs_src/programmers_guide/eager.md @@ -0,0 +1,992 @@ +# Eager Execution + +TensorFlow's eager execution is an imperative programming environment that +evaluates operations immediately, without an extra graph-building step. +Operations return concrete values instead of constructing a computational graph +to run later. This makes it easy to get started with TensorFlow, debug models, +reduce boilerplate code, and is fun! To follow along with this guide, run the +code samples below in an interactive `python` interpreter. + +Eager execution supports most TensorFlow operations and GPU acceleration. +Automatic differentiation uses a dynamically-constructed tape instead of a static +graph to compute gradients. Eager execution is a flexible machine learning +platform for research and experimentation that provides: + +* *An intuitive interface* —Structure your code naturally and use Python data + structures. Quickly iterate on small models and small data. +* *Easier debugging* —Call ops directly to inspect running models and test + changes. Use standard Python debugging tools for immediate error reporting. +* *Natural control flow* —Use Python control flow instead of graph control flow, + including support for dynamic models. + +For a collection of examples running in eager execution, see: +[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples). + +Note: Some models may experience increased overhead with eager execution enabled. +Performance improvements are ongoing, but please +[file a bug](https://github.com/tensorflow/tensorflow/issues) if you find a +problem and share your benchmarks. + +## Setup and basic usage + +Install TensorFlow 1.7 to include the updates for eager execution: + +``` +$ pip install --pre --upgrade tensorflow +``` + +To start eager execution, add `tf.enable_eager_execution()` to the beginning of +the program or console session. Do not add this operation to other modules that +the program calls. + +```py +from __future__ import absolute_import, division, print_function + +import tensorflow as tf + +tf.enable_eager_execution() +``` + +Now you can run TensorFlow operations and the results will return immediately: + +```py +tf.executing_eagerly() # => True + +x = [[2.]] +m = tf.matmul(x, x) +print("hello, {}".format(m)) # => "hello, [[4.]]" +``` + +Enabling eager execution changes how TensorFlow operations behave—now they +immediately evaluate and return their values to Python. `tf.Tensor` objects +reference concrete values instead of symbolic handles to nodes in a computational +graph. Since there isn't a computational graph to build and run later in a +session, it's easy to inspect results using `print()` or a debugger. Evaluating, +printing, and checking tensor values does not break the flow for computing +gradients. + +Eager execution works nicely with [NumPy](http://www.numpy.org/). NumPy +operations accept `tf.Tensor` arguments. TensorFlow +[math operations](https://www.tensorflow.org/api_guides/python/math_ops) convert +Python objects and NumPy arrays to `tf.Tensor` objects. The +`tf.Tensor.numpy` method returns the object's value as a NumPy `ndarray`. + +```py +a = tf.constant([[1, 2], + [3, 4]]) +print(a) +# => tf.Tensor([[1 2] +# [3 4]], shape=(2, 2), dtype=int32) + +# Broadcasting support +b = tf.add(a, 1) +print(b) +# => tf.Tensor([[2 3] +# [4 5]], shape=(2, 2), dtype=int32) + +# Operator overloading is supported +print(a * b) +# => tf.Tensor([[ 2 6] +# [12 20]], shape=(2, 2), dtype=int32) + +# Use NumPy values +import numpy as np + +c = np.multiply(a, b) +print(c) +# => [[ 2 6] +# [12 20]] + +# Obtain numpy value from a tensor: +print(a.numpy()) +# => [[1 2] +# [3 4]] +``` + +The `tfe` module contains symbols available to both eager and graph execution +environments and is useful for writing code to [work with graphs](#work_with_graphs): + +```py +import tensorflow.contrib.eager as tfe +``` + +## Eager training + +### Automatic differentiation + +[Automatic differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation) +is useful for implementing machine learning algorithms such as +[backpropagation](https://en.wikipedia.org/wiki/Backpropagation) for training +neural networks. During eager execution, use `tfe.GradientTape` to trace +operations for computing gradients later. + +`tfe.GradientTape` is an opt-in feature to provide maximal performance when +not tracing. Since different operations can occur during each call, all +forward-pass operations get recorded to a "tape". To compute the gradient, play +the tape backwards and then discard. A particular `tfe.GradientTape` can only +be computed once, subsequent calls throw a runtime error. + +```py +w = tfe.Variable([[1.0]]) +with tfe.GradientTape() as tape: + loss = w * w + +grad = tape.gradient(loss, [w]) +print(grad) # => [tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)] +``` + +Here's an example of `tfe.GradientTape` that records forward-pass operations +to train a simple model: + +```py +# A toy dataset of points around 3 * x + 2 +NUM_EXAMPLES = 1000 +training_inputs = tf.random_normal([NUM_EXAMPLES]) +noise = tf.random_normal([NUM_EXAMPLES]) +training_outputs = training_inputs * 3 + 2 + noise + +def prediction(input, weight, bias): + return input * weight + bias + +# A loss function using mean-squared error +def loss(weights, biases): + error = prediction(training_inputs, weights, biases) - training_outputs + return tf.reduce_mean(tf.square(error)) + +# Return the derivative of loss with respect to weight and bias +def grad(weights, biases): + with tfe.GradientTape() as tape: + loss_value = loss(weights, biases) + return tape.gradient(loss_value, [weights, biases]) + +train_steps = 200 +learning_rate = 0.01 +# Start with arbitrary values for W and B on the same batch of data +W = tfe.Variable(5.) +B = tfe.Variable(10.) + +print("Initial loss: {:.3f}".format(loss(W, B))) + +for i in range(train_steps): + dW, dB = grad(W, B) + W.assign_sub(dW * learning_rate) + B.assign_sub(dB * learning_rate) + if i % 20 == 0: + print("Loss at step {:03d}: {:.3f}".format(i, loss(W, B))) + +print("Final loss: {:.3f}".format(loss(W, B))) +print("W = {}, B = {}".format(W.numpy(), B.numpy())) +``` + +Output (exact numbers may vary): + +``` +Initial loss: 71.204 +Loss at step 000: 68.333 +Loss at step 020: 30.222 +Loss at step 040: 13.691 +Loss at step 060: 6.508 +Loss at step 080: 3.382 +Loss at step 100: 2.018 +Loss at step 120: 1.422 +Loss at step 140: 1.161 +Loss at step 160: 1.046 +Loss at step 180: 0.996 +Final loss: 0.974 +W = 3.01582956314, B = 2.1191945076 +``` + +Replay the `tfe.GradientTape` to compute the gradients and apply them in a +training loop. This is demonstrated in an excerpt from the +[mnist_eager.py](https://github.com/tensorflow/models/blob/master/official/mnist/mnist_eager.py) +example: + +```py +dataset = tf.data.Dataset.from_tensor_slices((data.train.images, + data.train.labels)) +... +for (batch, (images, labels)) in enumerate(tfe.Iterator(dataset)): + ... + with tfe.GradientTape() as tape: + logits = model(images, training=True) + loss_value = loss(logits, labels) + ... + grads = tape.gradient(loss_value, model.variables) + optimizer.apply_gradients(zip(grads, model.variables), + global_step=tf.train.get_or_create_global_step()) +``` + +#### Dynamic models + +`tfe.GradientTape` can also be used in dynamic models. This example for a +[backtracking line search](https://wikipedia.org/wiki/Backtracking_line_search) +algorithm looks like normal NumPy code, except there are gradients and is +differentiable, despite the complex control flow: + +```py +def line_search_step(fn, init_x, rate=1.0): + with tfe.GradientTape() as tape: + # Variables are automatically recorded, but manually watch a tensor + tape.watch(init_x) + value = fn(init_x) + grad, = tape.gradient(value, [init_x]) + grad_norm = tf.reduce_sum(grad * grad) + init_value = value + while value > init_value - rate * grad_norm: + x = init_x - rate * grad + value = fn(x) + rate /= 2.0 + return x, value +``` + +#### Additional functions to compute gradients + +`tfe.GradientTape` is a powerful interface for computing gradients, but there +is another [Autograd](https://github.com/HIPS/autograd)-style API available for +automatic differentiation. These functions are useful if writing math code with +only tensors and gradient functions, and without `tfe.Variables`: + +* `tfe.gradients_function` —Returns a function that computes the derivatives + of its input function parameter with respect to its arguments. The input + function parameter must return a scalar value. When the returned function is + invoked, it returns a list of `tf.Tensor` objects: one element for each + argument of the input function. Since anything of interest must be passed as a + function parameter, this becomes unwieldy if there's a dependency on many + trainable parameters. +* `tfe.value_and_gradients_function` —Similar to + `tfe.gradients_function`, but when the returned function is invoked, it + returns the value from the input function in addition to the list of + derivatives of the input function with respect to its arguments. + +In the following example, `tfe.gradients_function` takes the `square` +function as an argument and returns a function that computes the partial +derivatives of `square` with respect to its inputs. To calculate the derivative +of `square` at `3`, `grad(3.0)` returns `6`. + +```py +def square(x): + return tf.multiply(x, x) + +grad = tfe.gradients_function(square) + +square(3.) # => 9.0 +grad(3.) # => [6.0] + +# The second-order derivative of square: +gradgrad = tfe.gradients_function(lambda x: grad(x)[0]) +gradgrad(3.) # => [2.0] + +# The third-order derivative is None: +gradgradgrad = tfe.gradients_function(lambda x: gradgrad(x)[0]) +gradgradgrad(3.) # => [None] + + +# With flow control: +def abs(x): + return x if x > 0. else -x + +grad = tfe.gradients_function(abs) + +grad(3.) # => [1.0] +grad(-3.) # => [-1.0] +``` + +### Custom gradients + +Custom gradients are an easy way to override gradients in eager and graph +execution. Within the forward function, define the gradient with respect to the +inputs, outputs, or intermediate results. For example, here's an easy way to clip +the norm of the gradients in the backward pass: + +```py +@tf.custom_gradient +def clip_gradient_by_norm(x, norm): + y = tf.identity(x) + def grad_fn(dresult): + return [tf.clip_by_norm(dresult, norm), None] + return y, grad_fn +``` + +Custom gradients are commonly used to provide a numerically stable gradient for a +sequence of operations: + +```py +def log1pexp(x): + return tf.log(1 + tf.exp(x)) +grad_log1pexp = tfe.gradients_function(log1pexp) + +# The gradient computation works fine at x = 0. +grad_log1pexp(0.) # => [0.5] + +# However, x = 100 fails because of numerical instability. +grad_log1pexp(100.) # => [nan] +``` + + +Here, the `log1pexp` function can be analytically simplified with a custom +gradient. The implementation below reuses the value for `tf.exp(x)` that is +computed during the forward pass—making it more efficient by eliminating +redundant calculations: + +```py +@tfe.custom_gradient +def log1pexp(x): + e = tf.exp(x) + def grad(dy): + return dy * (1 - 1 / (1 + e)) + return tf.log(1 + e), grad + +grad_log1pexp = tfe.gradients_function(log1pexp) + +# As before, the gradient computation works fine at x = 0. +grad_log1pexp(0.) # => [0.5] + +# And the gradient computation also works at x = 100. +grad_log1pexp(100.) # => [1.0] +``` + + +## Build and train models + +There are many parameters to optimize when calculating derivatives. TensorFlow +code is easier to read when structured into reusable classes and objects instead +of a single top-level function. Eager execution encourages the use of the +Keras-style layer classes in the `tf.keras.layers` module. Additionally, the +`tf.train.Optimizer` classes provide sophisticated techniques to calculate +parameter updates. + +The following example creates a multi-layer model that classifies the standard +[MNIST handwritten digits](https://www.tensorflow.org/tutorials/layers). It +demonstrates the optimizer and layer APIs to build trainable graphs in an eager +execution environment. + +### Build a model + +The `tf.keras.Sequential` model is a linear stack of layers. It is easy to +use for basic models: + +```py +model = tf.keras.Sequential([ + tf.keras.layers.Dense(10, input_shape=(784,)), # must declare input shape + tf.keras.layers.Dense(10) +]) +``` + +Alternatively, organize models in classes by inheriting from `tf.keras.Model`. +This is a container for layers that is a layer itself, allowing `tf.keras.Model` +objects to contain other `tf.keras.Model` objects. + +```py +class MNISTModel(tf.keras.Model): + def __init__(self): + super(MNISTModel, self).__init__() + self.dense1 = tf.keras.layers.Dense(units=10) + self.dense2 = tf.keras.layers.Dense(units=10) + + def call(self, input): + """Run the model.""" + result = self.dense1(input) + result = self.dense2(result) + result = self.dense2(result) # reuse variables from dense2 layer + return result + +model = MNISTModel() +``` + +It's not required to set an input shape for the `tf.keras.Model` class since +the parameters are set the first time input is passed to the layer. + +`tf.keras.layers` classes create and contain their own model variables that +are tied to the lifetime of their layer objects. To share layer variables, share +their objects. + +### Train a model + +Even without training, call the model and inspect the output in eager execution: + +```py +# Create a tensor representing a blank image +batch = tf.zeros([1, 1, 784]) +print(batch.shape) # => (1, 1, 784) + +result = model(batch) +# => tf.Tensor([[[ 0. 0., ..., 0.]]], shape=(1, 1, 10), dtype=float32) +``` + +This example uses the +[dataset.py module](https://github.com/tensorflow/models/blob/master/official/mnist/dataset.py) +from the +[TensorFlow MNIST example](https://github.com/tensorflow/models/tree/master/official/mnist), +download this file to your local directory. Run the following to download the +MNIST data files to your working directory and prepare a `tf.data.Dataset` +for training: + +```py +import dataset # download dataset.py file +dataset_train = dataset.train('./datasets').shuffle(60000).repeat(4).batch(32) +``` + +To train a model, define a loss function to optimize and then calculate +gradients. Use an optimizer to update the variables: + +```py +def loss(model, x, y): + prediction = model(x) + return tf.losses.sparse_softmax_cross_entropy(labels=y, logits=prediction) + +def grad(model, inputs, targets): + with tfe.GradientTape() as tape: + loss_value = loss(model, inputs, targets) + return tape.gradient(loss_value, model.variables) + +optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001) + +x, y = tfe.Iterator(dataset_train).next() +print("Initial loss: {:.3f}".format(loss(model, x, y))) + +# Training loop +for (i, (x, y)) in enumerate(tfe.Iterator(dataset_train)): + # Calculate derivatives of the input function with respect to its parameters. + grads = grad(model, x, y) + # Apply the gradient to the model + optimizer.apply_gradients(zip(grads, model.variables), + global_step=tf.train.get_or_create_global_step()) + if i % 200 == 0: + print("Loss at step {:04d}: {:.3f}".format(i, loss(model, x, y))) + +print("Final loss: {:.3f}".format(loss(model, x, y))) +``` + +Output (exact numbers may vary): + +``` +Initial loss: 2.674 +Loss at step 0000: 2.593 +Loss at step 0200: 2.143 +Loss at step 0400: 2.009 +Loss at step 0600: 2.103 +Loss at step 0800: 1.621 +Loss at step 1000: 1.695 +... +Loss at step 6600: 0.602 +Loss at step 6800: 0.557 +Loss at step 7000: 0.499 +Loss at step 7200: 0.744 +Loss at step 7400: 0.681 +Final loss: 0.670 +``` + +And for faster training, move the computation to a GPU: + +```py +with tf.device("/gpu:0"): + for (i, (x, y)) in enumerate(tfe.Iterator(dataset_train)): + # minimize() is equivalent to the grad() and apply_gradients() calls. + optimizer.minimize(lambda: loss(model, x, y), + global_step=tf.train.get_or_create_global_step()) +``` + +### Variables and optimizers + +`tfe.Variable` objects store mutable `tf.Tensor` values accessed during +training to make automatic differentiation easier. The parameters of a model can +be encapsulated in classes as variables. + +Better encapsulate model parameters by using `tfe.Variable` with +`tfe.GradientTape`. For example, the automatic differentiation example above +can be rewritten: + +```py +class Model(tf.keras.Model): + def __init__(self): + super(Model, self).__init__() + self.W = tfe.Variable(5., name='weight') + self.B = tfe.Variable(10., name='bias') + def predict(self, inputs): + return inputs * self.W + self.B + +# A toy dataset of points around 3 * x + 2 +NUM_EXAMPLES = 2000 +training_inputs = tf.random_normal([NUM_EXAMPLES]) +noise = tf.random_normal([NUM_EXAMPLES]) +training_outputs = training_inputs * 3 + 2 + noise + +# The loss function to be optimized +def loss(model, inputs, targets): + error = model.predict(inputs) - targets + return tf.reduce_mean(tf.square(error)) + +def grad(model, inputs, targets): + with tfe.GradientTape() as tape: + loss_value = loss(model, inputs, targets) + return tape.gradient(loss_value, [model.W, model.B]) + +# Define: +# 1. A model. +# 2. Derivatives of a loss function with respect to model parameters. +# 3. A strategy for updating the variables based on the derivatives. +model = Model() +optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) + +print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs))) + +# Training loop +for i in range(300): + grads = grad(model, training_inputs, training_outputs) + optimizer.apply_gradients(zip(grads, [model.W, model.B]), + global_step=tf.train.get_or_create_global_step()) + if i % 20 == 0: + print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs))) + +print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs))) +print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy())) +``` + +Output (exact numbers may vary): + +``` +Initial loss: 69.066 +Loss at step 000: 66.368 +Loss at step 020: 30.107 +Loss at step 040: 13.959 +Loss at step 060: 6.769 +Loss at step 080: 3.567 +Loss at step 100: 2.141 +Loss at step 120: 1.506 +Loss at step 140: 1.223 +Loss at step 160: 1.097 +Loss at step 180: 1.041 +Loss at step 200: 1.016 +Loss at step 220: 1.005 +Loss at step 240: 1.000 +Loss at step 260: 0.998 +Loss at step 280: 0.997 +Final loss: 0.996 +W = 2.99431324005, B = 2.02129220963 +``` + +## Use objects for state during eager execution + +With graph execution, program state (such as the variables) is stored in global +collections and their lifetime is managed by the `tf.Session` object. In +contrast, during eager execution the lifetime of state objects is determined by +the lifetime of their corresponding Python object. + +### Variables are objects + +During eager execution, variables persist until the last reference to the object +is removed, and is then deleted. + +```py +with tf.device("gpu:0"): + v = tfe.Variable(tf.random_normal([1000, 1000])) + v = None # v no longer takes up GPU memory +``` + +### Object-based saving + +`tfe.Checkpoint` can save and restore `tfe.Variable`s to and from +checkpoints: + +```py +x = tfe.Variable(10.) + +checkpoint = tfe.Checkpoint(x=x) # save as "x" + +x.assign(2.) # Assign a new value to the variables and save. +save_path = checkpoint.save('./ckpt/') + +x.assign(11.) # Change the variable after saving. + +# Restore values from the checkpoint +checkpoint.restore(save_path) + +print(x) # => 2.0 +``` + +To save and load models, `tfe.Checkpoint` stores the internal state of objects, +without requiring hiiden variables. To record the state of a `model`, +an `optimizer`, and a global step, pass them to a `tfe.Checkpoint`: + +```py +model = MyModel() +optimizer = tf.train.AdamOptimizer(learning_rate=0.001) +checkpoint_dir = ‘/path/to/model_dir’ +checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") +root = tfe.Checkpoint(optimizer=optimizer, + model=model, + optimizer_step=tf.train.get_or_create_global_step()) + +root.save(file_prefix=checkpoint_prefix) +# or +root.restore(tf.train.latest_checkpoint(checkpoint_dir)) +``` + +### Object-oriented metrics + +`tfe.metrics` are stored as objects. Update a metric by passing the new data to +the callable, and retrieve the result using the `tfe.metrics.result` method, +for example: + +```py +m = tfe.metrics.Mean("loss") +m(0) +m(5) +m.result() # => 2.5 +m([8, 9]) +m.result() # => 5.5 +``` + +#### Summaries and TensorBoard + +@{$summaries_and_tensorboard$TensorBoard} is a visualization tool for +understanding, debugging and optimizing the model training process. It uses +summary events that are written while executing the program. + +`tf.contrib.summary` is compatible with both eager and graph execution +environments. Summary operations, such as `tf.contrib.summary.scalar`, are +inserted during model construction. For example, to record summaries once every +100 global steps: + +```py +tf.train.get_or_create_global_step() # return global step var +writer = tf.contrib.summary.create_file_writer(logdir) +global_step=tf.train.get_or_create_global_step() + +writer.set_as_default() + +for _ in range(iterations): + global_step.assign_add(1) + # Must include a record_summaries method + with tf.contrib.summary.record_summaries_every_n_global_steps(100): + # your model code goes here + tf.contrib.summary.scalar('loss', loss) + ... +``` + +## Performance + +Computation is not automatically offloaded to GPUs during eager execution. To +explicitly direct a computation to a GPU, enclose it in a +`tf.device('/gpu:0')` block: + +```py +import time + +def measure(x, steps): + # TensorFlow initializes a GPU the first time it's used, exclude from timing. + tf.matmul(x, x) + start = time.time() + for i in range(steps): + x = tf.matmul(x, x) + _ = x.numpy() # Make sure to execute op and not just enqueue it + end = time.time() + return end - start + +shape = (1000, 1000) +steps = 200 +print("Time to multiply a {} matrix by itself {} times:".format(shape, steps)) + +# Run on CPU: +with tf.device("/cpu:0"): + print("CPU: {} secs".format(measure(tf.random_normal(shape), steps))) + +# Run on GPU, if available: +if tfe.num_gpus() > 0: + with tf.device("/gpu:0"): + print("GPU: {} secs".format(measure(tf.random_normal(shape), steps))) +else: + print("GPU: not found") +``` + +Output (exact numbers depend on hardware): + +``` +Time to multiply a (1000, 1000) matrix by itself 200 times: +CPU: 4.614904403686523 secs +GPU: 0.5581181049346924 secs +``` + +A `tf.Tensor` object can be copied to a different device to execute its +operations: + +```py +x = tf.random_normal([10, 10]) + +x_gpu0 = x.gpu() +x_cpu = x.cpu() + +_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU +_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0 + +if tfe.num_gpus() > 1: + x_gpu1 = x.gpu(1) + _ = tf.matmul(x_gpu1, x_gpu1) # Runs on GPU:1 +``` + +### Benchmarks + +For compute-heavy models, such as +[ResNet50](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/resnet50) +training on a GPU, eager execution performance is comparable to graph execution. +But this gap grows larger for models with less computation and there is work to +be done for optimizing hot code paths for models with lots of small operations. + + +## Work with graphs + +While eager execution makes development and debugging more interactive, +TensorFlow graph execution has advantages for distributed training, performance +optimizations, and production deployment. However, writing graph code can feel +different than writing regular Python code and more difficult to debug. + +For building and training graph-constructed models, the Python program first +builds a graph representing the computation, then invokes `Session.run` to send +the graph for execution on the C++-based runtime. This provides: + +* Automatic differentiation using static autodiff. +* Simple deployment to a platform independent server. +* Graph-based optimizations (common subexpression elimination, constant-folding, etc.). +* Compilation and kernel fusion. +* Automatic distribution and replication (placing nodes on the distributed system). + +Deploying code written for eager execution is more difficult: either generate a +graph from the model, or run the Python runtime and code directly on the server. + +### Write compatible code + +The same code written for eager execution will also build a graph during graph +execution. Do this by simply running the same code in a new Python session where +eager execution is not enabled. + +Most TensorFlow operations work during eager execution, but there are some things +to keep in mind: + +* Use `tf.data` for input processing instead of queues. It's faster and easier. +* Use object-oriented layer APIs—like `tf.keras.layers` and + `tf.keras.Model`—since they have explicit storage for variables. +* Most model code works the same during eager and graph execution, but there are + exceptions. (For example, dynamic models using Python control flow to change the + computation based on inputs.) +* Once eager execution is enabled with `tf.enable_eager_execution`, it + cannot be turned off. Start a new Python session to return to graph execution. + +It's best to write code for both eager execution *and* graph execution. This +gives you eager's interactive experimentation and debuggability with the +distributed performance benefits of graph execution. + +Write, debug, and iterate in eager execution, then import the model graph for +production deployment. Use `tfe.Checkpoint` to save and restore model +variables, this allows movement between eager and graph execution environments. +See the examples in: +[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples). + +### Use eager execution in a graph environment + +Selectively enable eager execution in a TensorFlow graph environment using +`tfe.py_func`. This is used when `tf.enable_eager_execution()` has *not* +been called. + +```py +def my_py_func(x): + x = tf.matmul(x, x) # You can use tf ops + print(x) # but it's eager! + return x + +with tf.Session() as sess: + x = tf.placeholder(dtype=tf.float32) + # Call eager function in graph! + pf = tfe.py_func(my_py_func, [x], tf.float32) + sess.run(pf, feed_dict={x: [[2.0]]}) # [[4.0]] +``` + + +A `tfe.Checkpoint` stores the complete internal state of the objects passed to it. Nothing else is implicitly included. To record the state of a `model`, an `optimizer`, and a global step pass each one to the checkpoint's constructor: + +```py +model = MyModel() +optimizer = tf.train.AdamOptimizer(learning_rate=0.001) +checkpoint_dir = ‘/path/to/model_dir’ +checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") +root = tfe.Checkpoint(optimizer=optimizer, + model=model, + optimizer_step=tf.train.get_or_create_global_step()) + +root.save(file_prefix=checkpoint_prefix) +# or +root.restore(tf.train.latest_checkpoint(checkpoint_dir)) +``` + +### Object-oriented metrics + +`tfe.metrics` are stored as objects. Update a metric by passing the new data to +the callable, and retrieve the result using the `tfe.metrics.result` method, +for example: + +```py +m = tfe.metrics.Mean("loss") +m(0) +m(5) +m.result() # => 2.5 +m([8, 9]) +m.result() # => 5.5 +``` + +#### Summaries and TensorBoard + +@{$summaries_and_tensorboard$TensorBoard} is a visualization tool for +understanding, debugging and optimizing the model training process. It uses +summary events that are written while executing the program. + +`tf.contrib.summary` is compatible with both eager and graph execution +environments. Summary operations, such as `tf.contrib.summary.scalar`, are +inserted during model construction. For example, to record summaries once every +100 global steps: + +```py +tf.train.get_or_create_global_step() # return global step var +writer = tf.contrib.summary.create_file_writer(logdir) + +for _ in range(iterations): + with writer.as_default(): + with tf.contrib.summary.record_summaries_every_n_global_steps(100): + # your model code goes here + tf.contrib.summary.scalar('loss', loss) + ... +``` + +## Performance + +Computation is not automatically offloaded to GPUs during eager execution. To +explicitly direct a computation to a GPU, enclose it in a +`tf.device('/gpu:0')` block: + +```py +import time + +def measure(x, steps): + # TensorFlow initializes a GPU the first time it's used, exclude from timing. + tf.matmul(x, x) + start = time.time() + for i in range(steps): + x = tf.matmul(x, x) + _ = x.numpy() # Make sure to execute op and not just enqueue it + end = time.time() + return end - start + +shape = (1000, 1000) +steps = 200 +print("Time to multiply a {} matrix by itself {} times:".format(shape, steps)) + +# Run on CPU: +with tf.device("/cpu:0"): + print("CPU: {} secs".format(measure(tf.random_normal(shape), steps))) + +# Run on GPU, if available: +if tfe.num_gpus() > 0: + with tf.device("/gpu:0"): + print("GPU: {} secs".format(measure(tf.random_normal(shape), steps))) +else: + print("GPU: not found") +``` + +Output (exact numbers depend on hardware): + +``` +Time to multiply a (1000, 1000) matrix by itself 200 times: +CPU: 4.614904403686523 secs +GPU: 0.5581181049346924 secs +``` + +A `tf.Tensor` object can be copied to a different device to execute its +operations: + +```py +x = tf.random_normal([10, 10]) + +x_gpu0 = x.gpu() +x_cpu = x.cpu() + +_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU +_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0 + +if tfe.num_gpus() > 1: + x_gpu1 = x.gpu(1) + _ = tf.matmul(x_gpu1, x_gpu1) # Runs on GPU:1 +``` + +### Benchmarks + +For compute-heavy models, such as +[ResNet50](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/resnet50) +training on a GPU, eager execution performance is comparable to graph execution. +But this gap grows larger for models with less computation and there is work to +be done for optimizing hot code paths for models with lots of small operations. + + +## Work with graphs + +While eager execution makes development and debugging more interactive, +TensorFlow graph execution has advantages for distributed training, performance +optimizations, and production deployment. However, writing graph code can feel +different than writing regular Python code and more difficult to debug. + +For building and training graph-constructed models, the Python program first +builds a graph representing the computation, then invokes `Session.run` to send +the graph for execution on the C++-based runtime. This provides: + +* Automatic differentiation using static autodiff. +* Simple deployment to a platform independent server. +* Graph-based optimizations (common subexpression elimination, constant-folding, etc.). +* Compilation and kernel fusion. +* Automatic distribution and replication (placing nodes on the distributed system). + +Deploying code written for eager execution is more difficult: either generate a +graph from the model, or run the Python runtime and code directly on the server. + +### Write compatible code + +The same code written for eager execution will also build a graph during graph +execution. Do this by simply running the same code in a new Python session where +eager execution is not enabled. + +Most TensorFlow operations work during eager execution, but there are some things +to keep in mind: + +* Use `tf.data` for input processing instead of queues. It's faster and easier. +* Use object-oriented layer APIs—like `tf.keras.layers` and + `tf.keras.Model`—since they have explicit storage for variables. +* Most model code works the same during eager and graph execution, but there are + exceptions. (For example, dynamic models using Python control flow to change the + computation based on inputs.) +* Once eager execution is enabled with `tf.enable_eager_execution`, it + cannot be turned off. Start a new Python session to return to graph execution. + +It's best to write code for both eager execution *and* graph execution. This +gives you eager's interactive experimentation and debuggability with the +distributed performance benefits of graph execution. + +Write, debug, and iterate in eager execution, then import the model graph for +production deployment. Use `tfe.Checkpoint` to save and restore model +variables, this allows movement between eager and graph execution environments. +See the examples in: +[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples). + +### Use eager execution in a graph environment + +Selectively enable eager execution in a TensorFlow graph environment using +`tfe.py_func`. This is used when `tf.enable_eager_execution()` has *not* +been called. + +```py +def my_py_func(x): + x = tf.matmul(x, x) # You can use tf ops + print(x) # but it's eager! + return x + +with tf.Session() as sess: + x = tf.placeholder(dtype=tf.float32) + # Call eager function in graph! + pf = tfe.py_func(my_py_func, [x], tf.float32) + sess.run(pf, feed_dict={x: [[2.0]]}) # [[4.0]] +``` diff --git a/tensorflow/docs_src/programmers_guide/leftnav_files b/tensorflow/docs_src/programmers_guide/leftnav_files index 3fe4cb2dda..7ac63bf2e0 100644 --- a/tensorflow/docs_src/programmers_guide/leftnav_files +++ b/tensorflow/docs_src/programmers_guide/leftnav_files @@ -1,8 +1,9 @@ index.md ### High Level APIs -estimators.md +eager.md datasets.md +estimators.md ### Low Level APIs low_level_intro.md -- GitLab From c6911faaf4702096064542790d8c9e8e6f938d52 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Thu, 29 Mar 2018 13:35:34 -0700 Subject: [PATCH 436/906] Turns eager device placement on by default. Change the device policy to have silent copies, which are logged when RunMetadata tracking is enabled. In the process, changed TensorHandle to always keep its context around if it gets one. Changed TFE_TensorHandleResolve to, if necessary, copy to the CPU (since the user has no control as to whether this copy is needed by default). PiperOrigin-RevId: 190978086 --- tensorflow/c/eager/c_api.cc | 100 ++++++++++++------ tensorflow/c/eager/c_api.h | 18 ++-- tensorflow/c/eager/c_api_internal.h | 5 +- tensorflow/c/eager/c_api_test.cc | 10 +- .../core/common_runtime/eager/context.cc | 15 +-- .../core/common_runtime/eager/context.h | 16 +-- .../core/common_runtime/eager/execute.cc | 6 +- .../common_runtime/eager/tensor_handle.cc | 17 +-- .../core/common_runtime/eager/tensor_handle.h | 15 ++- tensorflow/core/kernels/function_ops.cc | 5 + tensorflow/python/eager/core_test.py | 12 +-- tensorflow/python/eager/function_test.py | 33 +++--- tensorflow/python/kernel_tests/BUILD | 4 + .../resource_variable_ops_test.py | 1 + 14 files changed, 148 insertions(+), 109 deletions(-) diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 028865d360..bb1492fca2 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -201,18 +201,24 @@ TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status) { const tensorflow::Tensor* t = nullptr; status->status = h->handle->TensorAndDevice(&t, &d, &op_device); if (!status->status.ok()) return nullptr; + tensorflow::TensorHandle* h_cpu = nullptr; if (!IsCPU(d)) { - TF_SetStatus(status, TF_UNIMPLEMENTED, - tensorflow::strings::StrCat( - "TFE_TensorHandle can be resolved iff it is on CPU (this " - "handle is on ", - d->name(), - "). Consider using TFE_TensorHandleCopyToDevice to get a " - "copy of the tensor on CPU") - .c_str()); - return nullptr; + status->status = h->handle->CopyToDevice( + h->handle->Context(), h->handle->Context()->HostCPU(), &h_cpu); + if (!status->status.ok()) { + return nullptr; + } + status->status = h_cpu->TensorAndDevice(&t, &d, &op_device); + if (!status->status.ok()) { + h_cpu->Unref(); + return nullptr; + } } - return tensorflow::TF_TensorFromTensor(*t, status); + TF_Tensor* retval = tensorflow::TF_TensorFromTensor(*t, status); + if (h_cpu != nullptr) { + h_cpu->Unref(); + } + return retval; } } // extern "C" @@ -258,17 +264,6 @@ void TFE_OpSetXLACompilation(TFE_Op* op, unsigned char enable) { } void TFE_OpAddInput(TFE_Op* op, TFE_TensorHandle* h, TF_Status* status) { - if (op->device == nullptr) { - // Questionable heuristic ... - // - If a device was explicitly set on the op, always use that. - // - If not, place on the first non-host device seen. - tensorflow::Device* d = nullptr; - // TODO(agarwal): This call may block if h is not ready. Avoid this if - // possible. - status->status = h->handle->Device(&d); - if (!status->status.ok()) return; - if (!IsCPU(d)) op->device = d; - } h->handle->Ref(); op->inputs.push_back(h->handle); op->attrs.NumInputs(op->inputs.size()); @@ -436,10 +431,39 @@ void TFE_OpSetAttrFunctionList(TFE_Op* op, const char* attr_name, namespace { +// Initializes the step stats if needed. +void MaybeInitializeStepStats(tensorflow::StepStats* step_stats, + tensorflow::EagerContext* ctx) { + // Lazily initialize the RunMetadata with information about all devices if + // this is the first call. + while (step_stats->dev_stats_size() < ctx->devices()->size()) { + int device_idx = step_stats->dev_stats_size(); + auto* dev_stats = step_stats->add_dev_stats(); + dev_stats->set_device(ctx->devices()->at(device_idx)->name()); + } +} + +int StepStatsDeviceIndex(tensorflow::StepStats* step_stats, + tensorflow::EagerContext* ctx, + tensorflow::Device* device) { + // Find the current device's index. + if (device == nullptr) { + device = ctx->HostCPU(); + } + for (int i = 0; i < ctx->devices()->size(); ++i) { + if (ctx->devices()->at(i) == device || + ctx->devices()->at(i)->name() == device->name()) { + return i; + } + } + // TODO(apassos) do not fall back to host CPU if device is unknown. + return 0; +} + tensorflow::Status ValidateInputTypeAndPlacement( - tensorflow::EagerContext* ctx, tensorflow::Device* host_device, - tensorflow::Device* op_device, TFE_Op* op, - const tensorflow::OpKernel* kernel) { + tensorflow::EagerContext* ctx, tensorflow::Device* op_device, TFE_Op* op, + const tensorflow::OpKernel* kernel, tensorflow::RunMetadata* run_metadata) { + tensorflow::Device* host_device = ctx->HostCPU(); const tensorflow::MemoryTypeVector& memtypes = kernel->input_memory_types(); if (memtypes.size() != op->inputs.size()) { return tensorflow::errors::InvalidArgument( @@ -489,9 +513,22 @@ tensorflow::Status ValidateInputTypeAndPlacement( } // We are only here if the policy is warn or silent copies, so we should // trigger a copy. + auto pre_time = tensorflow::Env::Default()->NowMicros(); tensorflow::TensorHandle* copied_tensor = nullptr; tensorflow::Status status = tensorflow::EagerCopyToDevice( handle, ctx, expected_device->name().c_str(), &copied_tensor); + if (run_metadata != nullptr) { + auto* step_stats = run_metadata->mutable_step_stats(); + MaybeInitializeStepStats(step_stats, ctx); + // Record the sending on the source device for now. + int device_idx = StepStatsDeviceIndex(step_stats, ctx, handle_device); + auto* dev_stats = step_stats->mutable_dev_stats(device_idx); + auto* node_stats = dev_stats->add_node_stats(); + node_stats->set_node_name("_Send"); + node_stats->set_all_start_micros(pre_time); + node_stats->set_op_end_rel_micros( + tensorflow::Env::Default()->NowMicros() - pre_time); + } if (!status.ok()) { if (copied_tensor != nullptr) copied_tensor->Unref(); return tensorflow::errors::Internal( @@ -785,8 +822,12 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, tensorflow::Device* input_op_device = nullptr; status->status = op->inputs[i]->OpDevice(&input_op_device); if (!status->status.ok()) return; + VLOG(2) << "for op " << op->name << " input " << i << " " + << tensorflow::DataTypeString(op->inputs[i]->dtype) << " " + << (input_op_device == nullptr ? "cpu" : input_op_device->name()) + << " " << (op->device == nullptr ? "cpu" : op->device->name()); if (op->inputs[i]->dtype == tensorflow::DT_RESOURCE && - input_op_device != op->device) { + (input_op_device != op->device || input_op_device == nullptr)) { tensorflow::Device* d = input_op_device == nullptr ? ctx->context.HostCPU() : input_op_device; VLOG(1) << "Changing device of operation " << op->name << " to " @@ -796,16 +837,13 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, } } tensorflow::Device* device = op->device; - if (!ctx->context.SoftPlacement() && device == nullptr) { - device = ctx->context.HostCPU(); - } tensorflow::Fprint128 cache_key = op->attrs.CacheKey(device == nullptr ? "unspecified" : device->name()); tensorflow::KernelAndDevice* kernel = ctx->context.GetCachedKernel(cache_key); if (kernel == nullptr) { const tensorflow::NodeDef& ndef = op->attrs.BuildNodeDef(); - if (ctx->context.SoftPlacement() && device == nullptr) { + if (device == nullptr) { device = SelectDevice(ndef, ctx, status); if (!status->status.ok()) { return; @@ -867,7 +905,9 @@ void TFE_Execute(TFE_Op* op, TFE_TensorHandle** retvals, int* num_retvals, device = kernel->device(); } status->status = ValidateInputTypeAndPlacement( - &ctx->context, ctx->context.HostCPU(), device, op, kernel->kernel()); + &ctx->context, device, op, kernel->kernel(), + ctx->context.ShouldStoreMetadata() ? ctx->context.RunMetadataProto() + : nullptr); if (!status->status.ok()) return; std::unique_ptr maybe_stats; if (ctx->context.ShouldStoreMetadata()) { diff --git a/tensorflow/c/eager/c_api.h b/tensorflow/c/eager/c_api.h index a5029bf211..3926c22ce1 100644 --- a/tensorflow/c/eager/c_api.h +++ b/tensorflow/c/eager/c_api.h @@ -61,17 +61,15 @@ TF_CAPI_EXPORT extern void TFE_ContextOptionsSetConfig( // Controls how to act when we try to run an operation on a given device but // some input tensors are not on that device. typedef enum TFE_ContextDevicePlacementPolicy { - // Running operations with input tensors on the wrong device will fail. When - // soft placement is enabled acts like TFE_DEVICE_PLACEMENT_SILENT. + // Running operations with input tensors on the wrong device will fail. TFE_DEVICE_PLACEMENT_EXPLICIT = 0, // Copy the tensor to the right device but log a warning. TFE_DEVICE_PLACEMENT_WARN = 1, - // Silently copy the tensor, which has a performance cost since the - // operation will be blocked till the copy completes. + // Silently copy the tensor, which has a performance cost since the operation + // will be blocked till the copy completes. This is the default placement + // policy. TFE_DEVICE_PLACEMENT_SILENT = 2, - // Default placement policy which silently copies int32 tensors but not other - // dtypes. When soft placement is enabled acts like - // TFE_DEVICE_PLACEMENT_SILENT. + // Placement policy which silently copies int32 tensors but not other dtypes. TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, } TFE_ContextDevicePlacementPolicy; @@ -162,7 +160,11 @@ TF_CAPI_EXPORT extern int64_t TFE_TensorHandleDim(TFE_TensorHandle* h, TF_CAPI_EXPORT extern const char* TFE_TensorHandleDeviceName( TFE_TensorHandle* h, TF_Status* status); -// This function will block till the operation that produces `h` has completed. +// This function will block till the operation that produces `h` has +// completed. The memory returned might alias the internal memory used by +// TensorFlow. Hence, callers should not mutate this memory (for example by +// modifying the memory region pointed to by TF_TensorData() on the returned +// TF_Tensor). TF_CAPI_EXPORT extern TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status); diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index e6d2ab75ff..05dc64f521 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -50,8 +50,7 @@ struct TFE_ContextOptions { TF_SessionOptions session_options; // true if async execution is enabled. bool async = false; - TFE_ContextDevicePlacementPolicy policy{ - TFE_DEVICE_PLACEMENT_SILENT_FOR_INT32}; + TFE_ContextDevicePlacementPolicy policy{TFE_DEVICE_PLACEMENT_SILENT}; }; struct TFE_Context { @@ -71,7 +70,7 @@ struct TFE_Context { struct TFE_TensorHandle { TFE_TensorHandle(const tensorflow::Tensor& t, tensorflow::Device* d, tensorflow::Device* op_device) - : handle(new tensorflow::TensorHandle(t, d, op_device)) {} + : handle(new tensorflow::TensorHandle(t, d, op_device, nullptr)) {} TFE_TensorHandle(tensorflow::uint64 node_id, tensorflow::DataType dtype, tensorflow::EagerContext* ctx) diff --git a/tensorflow/c/eager/c_api_test.cc b/tensorflow/c/eager/c_api_test.cc index d88a6c1dda..701175e494 100644 --- a/tensorflow/c/eager/c_api_test.cc +++ b/tensorflow/c/eager/c_api_test.cc @@ -590,7 +590,13 @@ void Execute_MatMul_CPU_Runtime_Error(bool async) { TFE_TensorHandle* m1 = TestMatrixTensorHandle(); TFE_TensorHandle* m2 = TestMatrixTensorHandle3X2(); TFE_Op* matmul = MatMulOp(ctx, m1, m2); + TFE_OpSetDevice(matmul, "/job:localhost/replica:0/task:0/device:CPU:0", + status); + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_Op* matmul2 = MatMulOp(ctx, m1, m1); + TFE_OpSetDevice(matmul2, "/job:localhost/replica:0/task:0/device:CPU:0", + status); + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_TensorHandle* retvals[1] = {nullptr}; int num_retvals = 1; TFE_Execute(matmul, &retvals[0], &num_retvals, status); @@ -693,14 +699,14 @@ TEST(CAPI, Execute_Min_CPU) { TF_Tensor* t = TFE_TensorHandleResolve(retvals[0], status); ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TFE_DeleteTensorHandle(retvals[0]); - TFE_DeleteContext(ctx, status); - ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); float output[2] = {0}; EXPECT_EQ(sizeof(output), TF_TensorByteSize(t)); memcpy(&output[0], TF_TensorData(t), TF_TensorByteSize(t)); TF_DeleteTensor(t); EXPECT_EQ(1, output[0]); EXPECT_EQ(3, output[1]); + TFE_DeleteContext(ctx, status); + ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); TF_DeleteStatus(status); } diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc index 0566329f18..9c47ad6187 100644 --- a/tensorflow/core/common_runtime/eager/context.cc +++ b/tensorflow/core/common_runtime/eager/context.cc @@ -17,24 +17,11 @@ limitations under the License. namespace tensorflow { -ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, ContextDevicePlacementPolicy original_policy) { - if (!soft_placement) { - return original_policy; - } - if (original_policy == DEVICE_PLACEMENT_EXPLICIT || - original_policy == DEVICE_PLACEMENT_SILENT_FOR_INT32) { - return DEVICE_PLACEMENT_SILENT; - } - return original_policy; -} - EagerContext::EagerContext(const SessionOptions& opts, ContextDevicePlacementPolicy default_policy, bool async, std::unique_ptr device_mgr, Rendezvous* rendezvous) - : soft_placement_(opts.config.allow_soft_placement()), - policy_(PlacementPolicy(soft_placement_, default_policy)), + : policy_(default_policy), device_manager_(std::move(device_mgr)), devices_(device_manager_->ListDevices()), rendezvous_(rendezvous), diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h index bc97219dae..a88fa5eaa4 100644 --- a/tensorflow/core/common_runtime/eager/context.h +++ b/tensorflow/core/common_runtime/eager/context.h @@ -43,23 +43,18 @@ namespace tensorflow { // Note: there's a copy enum in eager/c_api.h. It should be kept in sync. enum ContextDevicePlacementPolicy { - // Running operations with input tensors on the wrong device will fail. When - // soft placement is enabled acts like TFE_DEVICE_PLACEMENT_SILENT. + // Running operations with input tensors on the wrong device will fail. DEVICE_PLACEMENT_EXPLICIT = 0, // Copy the tensor to the right device but log a warning. DEVICE_PLACEMENT_WARN = 1, - // Silently copy the tensor, which has a performance cost since the - // operation will be blocked till the copy completes. + // Silently copy the tensor, which has a performance cost since the operation + // will be blocked till the copy completes. This is the default policy. DEVICE_PLACEMENT_SILENT = 2, // Default placement policy which silently copies int32 tensors but not other - // dtypes. When soft placement is enabled acts like - // TFE_DEVICE_PLACEMENT_SILENT. + // dtypes. DEVICE_PLACEMENT_SILENT_FOR_INT32 = 3, }; -ContextDevicePlacementPolicy PlacementPolicy( - bool soft_placement, ContextDevicePlacementPolicy original_policy); - class EagerContext { public: explicit EagerContext(const SessionOptions& opts, @@ -116,8 +111,6 @@ class EagerContext { Device* HostCPU() { return devices_[0]; } - bool SoftPlacement() { return soft_placement_; } - uint64 NextId() { return executor_.NextId(); } void ExecutorAdd(EagerNode* node) { executor_.Add(node); } @@ -148,7 +141,6 @@ class EagerContext { FunctionLibraryDefinition* FuncLibDef() { return &func_lib_def_; } private: - const bool soft_placement_; const ContextDevicePlacementPolicy policy_; // Note: we cannot use C++11 thread_local here as there is no concept of a diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index 4f16e42568..98e8471102 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -36,10 +36,6 @@ Status EagerExecute(EagerContext* ctx, Device* device, const gtl::InlinedVector& op_inputs, KernelAndDevice* kernel, NodeExecStats* maybe_stats, TensorHandle** retvals, int num_retvals) { - if (!ctx->SoftPlacement() && device == nullptr) { - device = ctx->HostCPU(); - } - if (device == nullptr) { // TODO(apassos) debug how the assignment below might return a different // device from the one requested above. @@ -100,7 +96,7 @@ Status EagerExecute(EagerContext* ctx, Device* device, d = nullptr; } if (retvals[i] == nullptr) { - retvals[i] = new TensorHandle(outputs[i], d, op_device); + retvals[i] = new TensorHandle(outputs[i], d, op_device, ctx); } else { retvals[i]->SetTensorAndDevice(outputs[i], d, op_device); } diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.cc b/tensorflow/core/common_runtime/eager/tensor_handle.cc index 328cd5dd5c..8e11f7b710 100644 --- a/tensorflow/core/common_runtime/eager/tensor_handle.cc +++ b/tensorflow/core/common_runtime/eager/tensor_handle.cc @@ -47,7 +47,7 @@ namespace tensorflow { bool TensorHandle::IsReady() { if (node_id == 0) return true; mutex_lock l(ctx_mutex_); - return ctx_ == nullptr; + return is_ready_; } Status TensorHandle::WaitReady() { @@ -55,7 +55,7 @@ Status TensorHandle::WaitReady() { EagerExecutor* executor = nullptr; { mutex_lock l(ctx_mutex_); - if (ctx_ == nullptr) return Status::OK(); + if (is_ready_) return Status::OK(); executor = ctx_->Executor(); } return executor->WaitFor(node_id); @@ -97,9 +97,10 @@ void TensorHandle::SetTensorAndDevice(const tensorflow::Tensor& tensor, tensorflow::Device* device, tensorflow::Device* op_device) { mutex_lock l(ctx_mutex_); - DCHECK(node_id > 0 && ctx_) << "SetTensorAndDevice should be only called " - << "on non-ready handles."; - ctx_ = nullptr; + DCHECK(node_id > 0 && !is_ready_) + << "SetTensorAndDevice should be only called " + << "on non-ready handles."; + is_ready_ = true; tensor_ = tensor; device_ = device; op_device_ = op_device; @@ -122,7 +123,7 @@ Status TensorHandle::CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, const bool both_on_cpu = src_cpu && dst_cpu; if (is_same_device || both_on_cpu) { dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(*src, dstd, dstd); + *output = new tensorflow::TensorHandle(*src, dstd, dstd, ctx); return tensorflow::Status::OK(); } if (!dst_cpu && (src->dtype() != tensorflow::DT_VARIANT && @@ -139,7 +140,7 @@ Status TensorHandle::CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, tensorflow::Tensor dst(dstd->GetAllocator(attr), src->dtype(), src->shape()); if (src->shape().num_elements() == 0) { dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(dst, dstd, dstd); + *output = new tensorflow::TensorHandle(dst, dstd, dstd, ctx); return tensorflow::Status::OK(); } tensorflow::DeviceContext* src_device_context = nullptr; @@ -170,7 +171,7 @@ Status TensorHandle::CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, n.WaitForNotification(); if (status.ok()) { dstd = dst_cpu ? nullptr : dstd; - *output = new tensorflow::TensorHandle(dst, dstd, dstd); + *output = new tensorflow::TensorHandle(dst, dstd, dstd, ctx); } return status; } diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.h b/tensorflow/core/common_runtime/eager/tensor_handle.h index eb69a13c06..d66c4d95e2 100644 --- a/tensorflow/core/common_runtime/eager/tensor_handle.h +++ b/tensorflow/core/common_runtime/eager/tensor_handle.h @@ -49,13 +49,14 @@ namespace tensorflow { // (unrelated to python TensorHandle). class TensorHandle : public core::RefCounted { public: - TensorHandle(const Tensor& t, Device* d, Device* op_device) + TensorHandle(const Tensor& t, Device* d, Device* op_device, EagerContext* ctx) : dtype(t.dtype()), node_id(0), tensor_(t), device_(d), op_device_(op_device), - ctx_(nullptr) {} + ctx_(ctx), + is_ready_(true) {} TensorHandle(uint64 node_id, DataType dtype, EagerContext* ctx) : dtype(dtype), @@ -63,7 +64,8 @@ class TensorHandle : public core::RefCounted { tensor_(dtype), device_(nullptr), op_device_(nullptr), - ctx_(ctx) { + ctx_(ctx), + is_ready_(ctx == nullptr) { DCHECK_GT(node_id, 0); } @@ -88,6 +90,12 @@ class TensorHandle : public core::RefCounted { Status CopyToDevice(EagerContext* ctx, tensorflow::Device* dstd, TensorHandle** output); + // Warning: can return nullptr for CPU tensors. + EagerContext* Context() { + mutex_lock ml(ctx_mutex_); + return ctx_; + } + // dtype for the handle. It must be the same as t.dtype() once the handle is // ready. const DataType dtype; @@ -126,6 +134,7 @@ class TensorHandle : public core::RefCounted { // typically true when the handle was produced during async execution. // `ctx` object is not owned and should outlive this handle. EagerContext* ctx_ GUARDED_BY(ctx_mutex_); + bool is_ready_ GUARDED_BY(ctx_mutex_); }; } // namespace tensorflow diff --git a/tensorflow/core/kernels/function_ops.cc b/tensorflow/core/kernels/function_ops.cc index 351aad7213..f8e0267578 100644 --- a/tensorflow/core/kernels/function_ops.cc +++ b/tensorflow/core/kernels/function_ops.cc @@ -144,6 +144,11 @@ TF_CALL_bool(REGISTER) REGISTER_KERNEL_BUILDER(Name(kRetOp) .HostMemory("input") .TypeConstraint("T"), RetvalOp); +REGISTER_KERNEL_BUILDER(Name(kRetOp) + .Device(DEVICE_GPU) + .TypeConstraint("T") + .HostMemory("input"), + RetvalOp); #undef REGISTER class PassOn : public OpKernel { diff --git a/tensorflow/python/eager/core_test.py b/tensorflow/python/eager/core_test.py index 5f19f64846..3fabe7060e 100644 --- a/tensorflow/python/eager/core_test.py +++ b/tensorflow/python/eager/core_test.py @@ -116,8 +116,7 @@ class TFETest(test_util.TensorFlowTestCase): cpu_stats = step_stats.dev_stats[0] self.assertEqual('/job:localhost/replica:0/task:0/device:CPU:0', cpu_stats.device) - self.assertEqual(len(cpu_stats.node_stats), 1) - self.assertEqual(cpu_stats.node_stats[0].node_name, 'Add') + self.assertGreaterEqual(len(cpu_stats.node_stats), 1) def testShouldCopy(self): if not context.context().num_gpus(): @@ -658,10 +657,11 @@ class SendRecvTest(test_util.TensorFlowTestCase): with ops.device('GPU:0'): t0 = constant_op.constant(1.0) self._send(t0, 't0', self.cpu_device) - self.assertAllEqual( - self._recv(dtypes.float32, 't0', gpu_device_name), - 1.0) - self._send(constant_op.constant(2.0), 't1', gpu_device_name) + with ops.device('cpu:0'): + self.assertAllEqual( + self._recv(dtypes.float32, 't0', gpu_device_name), + 1.0) + self._send(constant_op.constant(2.0), 't1', gpu_device_name) with ops.device('GPU:0'): self.assertAllEqual( self._recv(dtypes.float32, 't1', self.cpu_device), diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index fd1d2c25ff..9af197981b 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -26,7 +26,6 @@ from tensorflow.python.eager import tape from tensorflow.python.eager import test from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes -from tensorflow.python.framework import errors from tensorflow.python.framework import function as tf_function from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape @@ -377,23 +376,23 @@ class FunctionTest(test.TestCase): self.assertAllEqual(f(constant_op.constant(1.0)), 2.0) def testGradientOfGatherWithDefun(self): + with ops.device('cpu:0'): + v = resource_variable_ops.ResourceVariable([0.0, 1.0, 2.0]) - v = resource_variable_ops.ResourceVariable([0.0, 1.0, 2.0]) + def sum_gather(): + return math_ops.reduce_sum(array_ops.gather(v, [1, 2])) - def sum_gather(): - return math_ops.reduce_sum(array_ops.gather(v, [1, 2])) + grad_fn = backprop.implicit_grad(sum_gather) + gradient = grad_fn() + defun_grad_fn = backprop.implicit_grad(function.defun(sum_gather)) + defun_gradient = defun_grad_fn() + self.assertEqual(len(gradient), len(defun_gradient)) - grad_fn = backprop.implicit_grad(sum_gather) - gradient = grad_fn() - defun_grad_fn = backprop.implicit_grad(function.defun(sum_gather)) - defun_gradient = defun_grad_fn() - self.assertEqual(len(gradient), len(defun_gradient)) - - gradient = gradient[0][0] - defun_gradient = defun_gradient[0][0] - self.assertAllEqual(gradient.values, defun_gradient.values) - self.assertAllEqual(gradient.indices, defun_gradient.indices) - self.assertAllEqual(gradient.dense_shape, defun_gradient.dense_shape) + gradient = gradient[0][0] + defun_gradient = defun_gradient[0][0] + self.assertAllEqual(gradient.values, defun_gradient.values) + self.assertAllEqual(gradient.indices, defun_gradient.indices) + self.assertAllEqual(gradient.dense_shape, defun_gradient.dense_shape) def testReturningIndexedSlicesWithDefun(self): @@ -476,9 +475,7 @@ class FunctionTest(test.TestCase): reshape = function.defun(array_ops.reshape) value = constant_op.constant([1., 2.]) shape = constant_op.constant([2, 1]).gpu() - with self.assertRaises(errors.InvalidArgumentError): - with ops.device('gpu:0'): - reshape(value, shape) + reshape(value, shape) # No error is raised def testDifferentiableFunctionNoneOutputs(self): diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index ea210346c1..5eceb9f768 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -96,6 +96,10 @@ cuda_py_test( "//tensorflow/python:client_testlib", ], grpc_enabled = True, + tags = [ + "no_gpu", + "nogpu", + ], ) cuda_py_test( diff --git a/tensorflow/python/kernel_tests/resource_variable_ops_test.py b/tensorflow/python/kernel_tests/resource_variable_ops_test.py index 742564f9bf..c31d5a1f91 100644 --- a/tensorflow/python/kernel_tests/resource_variable_ops_test.py +++ b/tensorflow/python/kernel_tests/resource_variable_ops_test.py @@ -87,6 +87,7 @@ class ResourceVariableOpsTest(test_util.TensorFlowTestCase): with context.eager_mode(): handle = resource_variable_ops.var_handle_op( dtype=dtypes.int32, shape=[1], name="foo") + resource_variable_ops.assign_variable_op(handle, 1) with self.assertRaisesRegexp(errors.InvalidArgumentError, "Trying to read variable with wrong dtype. " "Expected float got int32."): -- GitLab From 1d5069d9f01d509ecd42614b056d3df4d4ba74ac Mon Sep 17 00:00:00 2001 From: Chris Tava Date: Thu, 29 Mar 2018 16:41:02 -0400 Subject: [PATCH 437/906] Updating install_golang.sh - bumping to 1.10 (#17989) --- tensorflow/tools/ci_build/install/install_golang.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/install/install_golang.sh b/tensorflow/tools/ci_build/install/install_golang.sh index e1edd62cc5..124ad82e91 100755 --- a/tensorflow/tools/ci_build/install/install_golang.sh +++ b/tensorflow/tools/ci_build/install/install_golang.sh @@ -16,7 +16,7 @@ set -ex -GOLANG_URL="https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz" +GOLANG_URL="https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz" sudo mkdir -p /usr/local wget -q -O - "${GOLANG_URL}" | sudo tar -C /usr/local -xz -- GitLab From 26fdbe7e8b3ec7fe799654cb72e849a6bfb3c5bf Mon Sep 17 00:00:00 2001 From: Michael Kuperstein Date: Thu, 29 Mar 2018 13:59:36 -0700 Subject: [PATCH 438/906] [XLA] Remove note about what implementations do for DynamicSlice and DynamicUpdateSlice. It is impossible to commit to a particular "implementation-defined behavior" for all implementations. PiperOrigin-RevId: 190981804 --- .../docs_src/performance/xla/operation_semantics.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tensorflow/docs_src/performance/xla/operation_semantics.md b/tensorflow/docs_src/performance/xla/operation_semantics.md index 32f249cf10..217ab596b7 100644 --- a/tensorflow/docs_src/performance/xla/operation_semantics.md +++ b/tensorflow/docs_src/performance/xla/operation_semantics.md @@ -788,9 +788,7 @@ DynamicSlice extracts a sub-array from the input array at dynamic dimension: [start, start + size). The shape of `start_indices` must be rank == 1, with dimension size equal to the rank of `operand`. Note: handling of out-of-bounds slice indices (generated by incorrect runtime -calculation of 'start_indices') is currently implementation-defined. Currently, -slice indices are computed modulo input dimension sizes to prevent out-of-bound -array accesses, but this behavior may change in future implementations. +calculation of 'start_indices') is currently implementation-defined. `DynamicSlice(operand, start_indices, size_indices)` @@ -847,9 +845,7 @@ is updated. The shape of `start_indices` must be rank == 1, with dimension size equal to the rank of `operand`. Note: handling of out-of-bounds slice indices (generated by incorrect runtime -calculation of 'start_indices') is currently implementation-defined. Currently, -slice indices are computed modulo update dimension sizes to prevent out-of-bound -array accesses, but this behavior may change in future implementations. +calculation of 'start_indices') is currently implementation-defined. `DynamicUpdateSlice(operand, update, start_indices)` -- GitLab From 1a9663e9e06075c5b5f8984bb95b36f3458edccf Mon Sep 17 00:00:00 2001 From: Younghee Kwon Date: Thu, 29 Mar 2018 14:17:16 -0700 Subject: [PATCH 439/906] boosted_trees: post-submit clean up - non-public objects are renamed. - is_single_machine is set properly when run_config is not populated properly (i.e. empty). PiperOrigin-RevId: 190984693 --- .../estimator/python/estimator/boosted_trees.py | 12 ++++++------ tensorflow/python/estimator/canned/boosted_trees.py | 12 ++++++------ .../python/estimator/canned/boosted_trees_test.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py index 5880164519..314c54ed00 100644 --- a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py +++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py @@ -67,20 +67,20 @@ class _BoostedTreesEstimator(estimator.Estimator): tree_complexity: regularization factor to penalize trees with more leaves. config: `RunConfig` object to configure the runtime settings. """ - # TODO(youngheek): param validations. - + # pylint:disable=protected-access # HParams for the model. - tree_hparams = canned_boosted_trees.TreeHParams( + tree_hparams = canned_boosted_trees._TreeHParams( n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, tree_complexity) def _model_fn(features, labels, mode, config): - return canned_boosted_trees._bt_model_fn( # pylint: disable=protected-access + return canned_boosted_trees._bt_model_fn( features, labels, mode, head, feature_columns, tree_hparams, n_batches_per_layer, config) super(_BoostedTreesEstimator, self).__init__( model_fn=_model_fn, model_dir=model_dir, config=config) + # pylint:enable=protected-access def boosted_trees_classifier_train_in_memory( @@ -182,7 +182,7 @@ def boosted_trees_classifier_train_in_memory( n_classes, weight_column, label_vocabulary=label_vocabulary)) # HParams for the model. - tree_hparams = canned_boosted_trees.TreeHParams( + tree_hparams = canned_boosted_trees._TreeHParams( n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, tree_complexity) @@ -298,7 +298,7 @@ def boosted_trees_regressor_train_in_memory( weight_column) # HParams for the model. - tree_hparams = canned_boosted_trees.TreeHParams( + tree_hparams = canned_boosted_trees._TreeHParams( n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, tree_complexity) diff --git a/tensorflow/python/estimator/canned/boosted_trees.py b/tensorflow/python/estimator/canned/boosted_trees.py index a9bbabd598..7f1bcc31f2 100644 --- a/tensorflow/python/estimator/canned/boosted_trees.py +++ b/tensorflow/python/estimator/canned/boosted_trees.py @@ -40,7 +40,7 @@ from tensorflow.python.training import session_run_hook from tensorflow.python.training import training_util from tensorflow.python.util.tf_export import tf_export -TreeHParams = collections.namedtuple( +_TreeHParams = collections.namedtuple( 'TreeHParams', ['n_trees', 'max_depth', 'learning_rate', 'l1', 'l2', 'tree_complexity']) @@ -259,8 +259,8 @@ def _bt_model_fn( example_id_column_name=None, # TODO(youngheek): replace this later using other options. train_in_memory=False, - name='TreeEnsembleModel'): - """Gradient Boosted Decision Tree model_fn. + name='boosted_trees'): + """Gradient Boosted Trees model_fn. Args: features: dict of `Tensor`. @@ -290,7 +290,7 @@ def _bt_model_fn( Raises: ValueError: mode or params are invalid, or features has the wrong type. """ - is_single_machine = (config.num_worker_replicas == 1) + is_single_machine = (config.num_worker_replicas <= 1) if train_in_memory: assert n_batches_per_layer == 1, ( 'When train_in_memory is enabled, input_fn should return the entire ' @@ -617,7 +617,7 @@ class BoostedTreesClassifier(estimator.Estimator): n_classes, weight_column, label_vocabulary=label_vocabulary) # HParams for the model. - tree_hparams = TreeHParams( + tree_hparams = _TreeHParams( n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, tree_complexity) @@ -723,7 +723,7 @@ class BoostedTreesRegressor(estimator.Estimator): head = _create_regression_head(label_dimension, weight_column) # HParams for the model. - tree_hparams = TreeHParams( + tree_hparams = _TreeHParams( n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, tree_complexity) diff --git a/tensorflow/python/estimator/canned/boosted_trees_test.py b/tensorflow/python/estimator/canned/boosted_trees_test.py index 9276fbaaa1..01e5cc7a5d 100644 --- a/tensorflow/python/estimator/canned/boosted_trees_test.py +++ b/tensorflow/python/estimator/canned/boosted_trees_test.py @@ -195,7 +195,7 @@ class ModelFnTests(test_util.TensorFlowTestCase): feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), BUCKET_BOUNDARIES) for i in range(NUM_FEATURES) } - self._tree_hparams = boosted_trees.TreeHParams( + self._tree_hparams = boosted_trees._TreeHParams( # pylint:disable=protected-access n_trees=2, max_depth=2, learning_rate=0.1, -- GitLab From 489389822636b1229c2e92b717c3e947ccfa23b4 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 14:25:10 -0700 Subject: [PATCH 440/906] LSTM support: Add non-uint8 quantized elementwise unary operators. PiperOrigin-RevId: 190986046 --- .../internal/optimized/optimized_ops.h | 154 +++++++++++++++--- 1 file changed, 128 insertions(+), 26 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h index 4661004d09..3642da311c 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h @@ -4092,12 +4092,46 @@ inline void Logistic(const uint8* input_data, const Dims<4>& input_dims, inline void Logistic(const int16* input_data, const Dims<4>& input_dims, int16* output_data, const Dims<4>& output_dims) { gemmlowp::ScopedProfilingLabel label("Logistic/Int16"); - // This is a copy of the reference implementation. We do not currently have a - // properly optimized version. const int flat_size = RequiredBufferSizeForDims(output_dims); TFLITE_DCHECK_EQ(RequiredBufferSizeForDims(input_dims), flat_size); for (int i = 0; i < flat_size; i++) { + } + + int c = 0; + const int16* input_data_ptr = input_data; + int16* output_data_ptr = output_data; +#ifdef GEMMLOWP_NEON + { + // F0 uses 0 integer bits, range [-1, 1]. + // This is the return type of math functions such as tanh, logistic, + // whose range is in [-1, 1]. + using F0 = gemmlowp::FixedPoint; + // F3 uses 3 integer bits, range [-8, 8], the input range expected here. + using F3 = gemmlowp::FixedPoint; + + for (; c <= flat_size - 16; c += 16) { + F3 input0 = F3::FromRaw(vld1q_s16(input_data_ptr)); + F3 input1 = F3::FromRaw(vld1q_s16(input_data_ptr + 8)); + F0 output0 = gemmlowp::logistic(input0); + F0 output1 = gemmlowp::logistic(input1); + vst1q_s16(output_data_ptr, output0.raw()); + vst1q_s16(output_data_ptr + 8, output1.raw()); + + input_data_ptr += 16; + output_data_ptr += 16; + } + for (; c <= flat_size - 8; c += 8) { + F3 input = F3::FromRaw(vld1q_s16(input_data_ptr)); + F0 output = gemmlowp::logistic(input); + vst1q_s16(output_data_ptr, output.raw()); + + input_data_ptr += 8; + output_data_ptr += 8; + } + } +#endif + { // F0 uses 0 integer bits, range [-1, 1]. // This is the return type of math functions such as tanh, logistic, // whose range is in [-1, 1]. @@ -4105,9 +4139,14 @@ inline void Logistic(const int16* input_data, const Dims<4>& input_dims, // F3 uses 3 integer bits, range [-8, 8], the input range expected here. using F3 = gemmlowp::FixedPoint; - const F3 input = F3::FromRaw(input_data[i]); - F0 output = gemmlowp::logistic(input); - output_data[i] = output.raw(); + for (; c < flat_size; ++c) { + F3 input = F3::FromRaw(*input_data_ptr); + F0 output = gemmlowp::logistic(input); + *output_data_ptr = output.raw(); + + ++input_data_ptr; + ++output_data_ptr; + } } } @@ -4274,9 +4313,6 @@ inline void Tanh(const int16* input_data, const Dims<4>& input_dims, int input_left_shift, int16* output_data, const Dims<4>& output_dims) { gemmlowp::ScopedProfilingLabel label("Tanh/Int16"); - // This is a copy of the reference implementation. We do not currently have a - // properly optimized version. - // Support for shifts is limited until we have a parameterized version of // SaturatingRoundingMultiplyByPOT(). TFLITE_DCHECK_GE(input_left_shift, 0); @@ -4285,25 +4321,91 @@ inline void Tanh(const int16* input_data, const Dims<4>& input_dims, const int flat_size = RequiredBufferSizeForDims(output_dims); TFLITE_DCHECK_EQ(RequiredBufferSizeForDims(input_dims), flat_size); - // F0 uses 0 integer bits, range [-1, 1]. - // This is the return type of math functions such as tanh, logistic, - // whose range is in [-1, 1]. - using F0 = gemmlowp::FixedPoint; - // F3 uses 3 integer bits, range [-8, 8], the input range expected here. - using F3 = gemmlowp::FixedPoint; - - if (input_left_shift == 0) { - for (int i = 0; i < flat_size; i++) { - F3 input = F3::FromRaw(input_data[i]); - F0 output = gemmlowp::tanh(input); - output_data[i] = output.raw(); + int c = 0; + const int16* input_data_ptr = input_data; + int16* output_data_ptr = output_data; +#ifdef GEMMLOWP_NEON + { + // F0 uses 0 integer bits, range [-1, 1]. + // This is the return type of math functions such as tanh, logistic, + // whose range is in [-1, 1]. + using F0 = gemmlowp::FixedPoint; + // F3 uses 3 integer bits, range [-8, 8], the input range expected here. + using F3 = gemmlowp::FixedPoint; + + if (input_left_shift == 0) { + for (; c <= flat_size - 16; c += 16) { + F3 input0 = F3::FromRaw(vld1q_s16(input_data_ptr)); + F3 input1 = F3::FromRaw(vld1q_s16(input_data_ptr + 8)); + F0 output0 = gemmlowp::tanh(input0); + F0 output1 = gemmlowp::tanh(input1); + vst1q_s16(output_data_ptr, output0.raw()); + vst1q_s16(output_data_ptr + 8, output1.raw()); + + input_data_ptr += 16; + output_data_ptr += 16; + } + for (; c <= flat_size - 8; c += 8) { + F3 input = F3::FromRaw(vld1q_s16(input_data_ptr)); + F0 output = gemmlowp::tanh(input); + vst1q_s16(output_data_ptr, output.raw()); + + input_data_ptr += 8; + output_data_ptr += 8; + } + } else { + for (; c <= flat_size - 16; c += 16) { + F3 input0 = F3::FromRaw(gemmlowp::SaturatingRoundingMultiplyByPOT<1>( + vld1q_s16(input_data_ptr))); + F3 input1 = F3::FromRaw(gemmlowp::SaturatingRoundingMultiplyByPOT<1>( + vld1q_s16(input_data_ptr + 8))); + F0 output0 = gemmlowp::tanh(input0); + F0 output1 = gemmlowp::tanh(input1); + vst1q_s16(output_data_ptr, output0.raw()); + vst1q_s16(output_data_ptr + 8, output1.raw()); + + input_data_ptr += 16; + output_data_ptr += 16; + } + for (; c <= flat_size - 8; c += 8) { + F3 input = F3::FromRaw(gemmlowp::SaturatingRoundingMultiplyByPOT<1>( + vld1q_s16(input_data_ptr))); + F0 output = gemmlowp::tanh(input); + vst1q_s16(output_data_ptr, output.raw()); + + input_data_ptr += 8; + output_data_ptr += 8; + } } - } else { - for (int i = 0; i < flat_size; i++) { - F3 input = F3::FromRaw( - gemmlowp::SaturatingRoundingMultiplyByPOT<1>(input_data[i])); - F0 output = gemmlowp::tanh(input); - output_data[i] = output.raw(); + } +#endif + { + // F0 uses 0 integer bits, range [-1, 1]. + // This is the return type of math functions such as tanh, logistic, + // whose range is in [-1, 1]. + using F0 = gemmlowp::FixedPoint; + // F3 uses 3 integer bits, range [-8, 8], the input range expected here. + using F3 = gemmlowp::FixedPoint; + + if (input_left_shift == 0) { + for (; c < flat_size; ++c) { + F3 input = F3::FromRaw(*input_data_ptr); + F0 output = gemmlowp::tanh(input); + *output_data_ptr = output.raw(); + + ++input_data_ptr; + ++output_data_ptr; + } + } else { + for (; c < flat_size; ++c) { + F3 input = F3::FromRaw( + gemmlowp::SaturatingRoundingMultiplyByPOT<1>(*input_data_ptr)); + F0 output = gemmlowp::tanh(input); + *output_data_ptr = output.raw(); + + ++input_data_ptr; + ++output_data_ptr; + } } } } -- GitLab From 79e4a49f7bb458176cbfa5ba1e492b39dada023d Mon Sep 17 00:00:00 2001 From: Eugene Brevdo Date: Thu, 29 Mar 2018 15:05:31 -0700 Subject: [PATCH 441/906] TFLite logs to stderr. PiperOrigin-RevId: 190992629 --- tensorflow/contrib/lite/nnapi/NeuralNetworksShim.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/lite/nnapi/NeuralNetworksShim.h b/tensorflow/contrib/lite/nnapi/NeuralNetworksShim.h index bd49d327c9..85aca36874 100644 --- a/tensorflow/contrib/lite/nnapi/NeuralNetworksShim.h +++ b/tensorflow/contrib/lite/nnapi/NeuralNetworksShim.h @@ -22,7 +22,7 @@ limitations under the License. // helpers -#define NNAPI_LOG(format, ...) printf(format "\n", __VA_ARGS__); +#define NNAPI_LOG(format, ...) fprintf(stderr, format "\n", __VA_ARGS__); #define LOAD_FUNCTION(name) \ static name##_fn fn = reinterpret_cast(loadFunction(#name)); #define EXECUTE_FUNCTION(...) \ -- GitLab From 4b8f6dc1efec882c3fb0e2c8fc3de74586c800ce Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 15:18:59 -0700 Subject: [PATCH 442/906] [XLA] Remove some dead code from Executable. PiperOrigin-RevId: 190994733 --- tensorflow/compiler/xla/service/BUILD | 1 - tensorflow/compiler/xla/service/cpu/cpu_executable.h | 5 ----- .../compiler/xla/service/cpu/parallel_cpu_executable.h | 6 ------ tensorflow/compiler/xla/service/executable.h | 9 --------- tensorflow/compiler/xla/service/gpu/gpu_executable.h | 5 ----- 5 files changed, 26 deletions(-) diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index b7d1bf64d0..3a99d84bea 100644 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -730,7 +730,6 @@ cc_library( ":computation_layout", ":device_memory_allocator", ":hlo", - ":hlo_cost_analysis", ":hlo_execution_profile", ":hlo_graph_dumper", ":pool", diff --git a/tensorflow/compiler/xla/service/cpu/cpu_executable.h b/tensorflow/compiler/xla/service/cpu/cpu_executable.h index 267b89a10b..d3502b3a03 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_executable.h +++ b/tensorflow/compiler/xla/service/cpu/cpu_executable.h @@ -71,11 +71,6 @@ class CpuExecutable : public Executable { ir_module_string_ = ir_module_string; } - const Status EqualOrFail(const Executable& executable) { - // TODO(b/62952745) Implement equality test on CPU executable. - return Unimplemented("Equality test on CPU executable is not implemented."); - } - static int64 ShapeSizeBytes(const Shape& shape); // Type of the computation function we expect in the JIT. diff --git a/tensorflow/compiler/xla/service/cpu/parallel_cpu_executable.h b/tensorflow/compiler/xla/service/cpu/parallel_cpu_executable.h index c393e9b8ea..87c0a3df45 100644 --- a/tensorflow/compiler/xla/service/cpu/parallel_cpu_executable.h +++ b/tensorflow/compiler/xla/service/cpu/parallel_cpu_executable.h @@ -83,12 +83,6 @@ class ParallelCpuExecutable : public Executable { return ShapeUtil::ByteSizeOf(shape, sizeof(void*)); } - const Status EqualOrFail(const Executable& executable) { - // TODO(b/62952745) Implement equality test on CPU parallel executable. - return Unimplemented( - "Equality test on CPU parallel executable is not implemented."); - } - private: // Allocate buffers required for execution and assign them to the elements of // "buffers". "buffers" should be sized to the number of buffers in buffer diff --git a/tensorflow/compiler/xla/service/executable.h b/tensorflow/compiler/xla/service/executable.h index 0aee535ee7..a157235f8a 100644 --- a/tensorflow/compiler/xla/service/executable.h +++ b/tensorflow/compiler/xla/service/executable.h @@ -22,7 +22,6 @@ limitations under the License. #include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h" #include "tensorflow/compiler/xla/service/computation_layout.h" #include "tensorflow/compiler/xla/service/device_memory_allocator.h" -#include "tensorflow/compiler/xla/service/hlo_cost_analysis.h" #include "tensorflow/compiler/xla/service/hlo_execution_profile.h" #include "tensorflow/compiler/xla/service/hlo_graph_dumper.h" #include "tensorflow/compiler/xla/service/hlo_module.h" @@ -109,14 +108,6 @@ class Executable { return execution_profile_; } - // Returns Status::ok() if the two executables are equal to each other. - // - // An error status is returned otherwise. - virtual const Status EqualOrFail(const Executable& executable) { - return Unimplemented( - "Equality test on this executable is not implemented."); - } - const HloProfilePrinterData& hlo_profile_printer_data() const { CHECK(hlo_profiling_enabled()); return *hlo_profile_printer_data_; diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.h b/tensorflow/compiler/xla/service/gpu/gpu_executable.h index b19cfd43de..dcb3991f41 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_executable.h +++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.h @@ -83,11 +83,6 @@ class GpuExecutable : public Executable { const ServiceExecutableRunOptions* run_options, tensorflow::gtl::ArraySlice arguments) override; - const Status EqualOrFail(const Executable& executable) { - // TODO(b/62952745) Implement equality test on GPU executable. - return Unimplemented("Equality test on GPU executable is not implemented."); - } - private: // If `block_host_until_done` is false, execution will not block the host // until the kernels have completed. This is used as an optimization for -- GitLab From 40f8291db5c0b05b31d7bbe23b847cdbb2408718 Mon Sep 17 00:00:00 2001 From: Anna R Date: Thu, 29 Mar 2018 15:20:38 -0700 Subject: [PATCH 443/906] Internal change. PiperOrigin-RevId: 190995029 --- tensorflow/contrib/boosted_trees/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/boosted_trees/BUILD b/tensorflow/contrib/boosted_trees/BUILD index ddeda0079c..8eac1243ef 100644 --- a/tensorflow/contrib/boosted_trees/BUILD +++ b/tensorflow/contrib/boosted_trees/BUILD @@ -119,7 +119,7 @@ py_library( py_test( name = "gbdt_batch_test", - size = "small", + size = "medium", srcs = ["python/training/functions/gbdt_batch_test.py"], srcs_version = "PY2AND3", tags = [ -- GitLab From 6f5d7a97cd2c0741ddfa756853ce5321377b5d53 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 15:28:24 -0700 Subject: [PATCH 444/906] Add tf.contrib.distribute, which defines classes DistributionStrategy and MirroredStrategy, and related functionality. Also add tf.contrib.optimizer_v2, an update to the Optimizer API. RELNOTES: Can now pass tf.contrib.distribute.MirroredStrategy() to tf.estimator.RunConfig() to run an Estimator model on multiple GPUs on one machine. PiperOrigin-RevId: 190996247 --- tensorflow/contrib/BUILD | 2 + tensorflow/contrib/__init__.py | 2 + tensorflow/contrib/cmake/python_modules.txt | 3 + tensorflow/contrib/distribute/BUILD | 36 + tensorflow/contrib/distribute/__init__.py | 52 + tensorflow/contrib/distribute/python/BUILD | 431 ++++++ .../contrib/distribute/python/combinations.py | 293 ++++ .../distribute/python/combinations_test.py | 115 ++ .../distribute/python/cross_tower_ops.py | 410 +++++ .../distribute/python/cross_tower_ops_test.py | 185 +++ .../distribute/python/cross_tower_utils.py | 153 ++ .../distribute/python/minimize_loss_test.py | 279 ++++ .../distribute/python/mirrored_strategy.py | 486 ++++++ .../python/mirrored_strategy_multigpu_test.py | 435 ++++++ .../python/mirrored_strategy_test.py | 91 ++ .../contrib/distribute/python/monitor.py | 61 + .../contrib/distribute/python/monitor_test.py | 84 + .../distribute/python/one_device_strategy.py | 148 ++ .../python/one_device_strategy_test.py | 54 + .../distribute/python/optimizer_v2_test.py | 70 + .../distribute/python/prefetching_ops_v2.py | 167 ++ .../python/prefetching_ops_v2_test.py | 68 + .../python/shared_variable_creator.py | 97 ++ .../python/shared_variable_creator_test.py | 75 + .../python/simple_estimator_example.py | 97 ++ .../distribute/python/single_loss_example.py | 102 ++ .../contrib/distribute/python/step_fn.py | 103 ++ .../contrib/distribute/python/step_fn_test.py | 62 + .../distribute/python/strategy_test_lib.py | 225 +++ .../contrib/distribute/python/values.py | 575 +++++++ .../contrib/distribute/python/values_test.py | 807 ++++++++++ tensorflow/contrib/optimizer_v2/BUILD | 205 +++ tensorflow/contrib/optimizer_v2/adadelta.py | 113 ++ .../contrib/optimizer_v2/adadelta_test.py | 167 ++ tensorflow/contrib/optimizer_v2/adagrad.py | 118 ++ .../contrib/optimizer_v2/adagrad_test.py | 282 ++++ tensorflow/contrib/optimizer_v2/adam.py | 202 +++ tensorflow/contrib/optimizer_v2/adam_test.py | 333 ++++ .../optimizer_v2/checkpointable_utils_test.py | 686 +++++++++ .../contrib/optimizer_v2/gradient_descent.py | 69 + .../optimizer_v2/gradient_descent_test.py | 223 +++ tensorflow/contrib/optimizer_v2/momentum.py | 124 ++ .../contrib/optimizer_v2/momentum_test.py | 562 +++++++ .../contrib/optimizer_v2/optimizer_v2.py | 1352 +++++++++++++++++ .../optimizer_v2/optimizer_v2_symbols.py | 42 + .../contrib/optimizer_v2/optimizer_v2_test.py | 294 ++++ tensorflow/contrib/optimizer_v2/rmsprop.py | 233 +++ .../contrib/optimizer_v2/rmsprop_test.py | 449 ++++++ tensorflow/python/training/distribute.py | 7 +- tensorflow/tools/docs/generate_lib.py | 1 - 50 files changed, 11226 insertions(+), 4 deletions(-) create mode 100644 tensorflow/contrib/distribute/BUILD create mode 100644 tensorflow/contrib/distribute/__init__.py create mode 100644 tensorflow/contrib/distribute/python/BUILD create mode 100644 tensorflow/contrib/distribute/python/combinations.py create mode 100644 tensorflow/contrib/distribute/python/combinations_test.py create mode 100644 tensorflow/contrib/distribute/python/cross_tower_ops.py create mode 100644 tensorflow/contrib/distribute/python/cross_tower_ops_test.py create mode 100644 tensorflow/contrib/distribute/python/cross_tower_utils.py create mode 100644 tensorflow/contrib/distribute/python/minimize_loss_test.py create mode 100644 tensorflow/contrib/distribute/python/mirrored_strategy.py create mode 100644 tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py create mode 100644 tensorflow/contrib/distribute/python/mirrored_strategy_test.py create mode 100644 tensorflow/contrib/distribute/python/monitor.py create mode 100644 tensorflow/contrib/distribute/python/monitor_test.py create mode 100644 tensorflow/contrib/distribute/python/one_device_strategy.py create mode 100644 tensorflow/contrib/distribute/python/one_device_strategy_test.py create mode 100644 tensorflow/contrib/distribute/python/optimizer_v2_test.py create mode 100644 tensorflow/contrib/distribute/python/prefetching_ops_v2.py create mode 100644 tensorflow/contrib/distribute/python/prefetching_ops_v2_test.py create mode 100644 tensorflow/contrib/distribute/python/shared_variable_creator.py create mode 100644 tensorflow/contrib/distribute/python/shared_variable_creator_test.py create mode 100644 tensorflow/contrib/distribute/python/simple_estimator_example.py create mode 100644 tensorflow/contrib/distribute/python/single_loss_example.py create mode 100644 tensorflow/contrib/distribute/python/step_fn.py create mode 100644 tensorflow/contrib/distribute/python/step_fn_test.py create mode 100644 tensorflow/contrib/distribute/python/strategy_test_lib.py create mode 100644 tensorflow/contrib/distribute/python/values.py create mode 100644 tensorflow/contrib/distribute/python/values_test.py create mode 100644 tensorflow/contrib/optimizer_v2/BUILD create mode 100644 tensorflow/contrib/optimizer_v2/adadelta.py create mode 100644 tensorflow/contrib/optimizer_v2/adadelta_test.py create mode 100644 tensorflow/contrib/optimizer_v2/adagrad.py create mode 100644 tensorflow/contrib/optimizer_v2/adagrad_test.py create mode 100644 tensorflow/contrib/optimizer_v2/adam.py create mode 100644 tensorflow/contrib/optimizer_v2/adam_test.py create mode 100644 tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py create mode 100644 tensorflow/contrib/optimizer_v2/gradient_descent.py create mode 100644 tensorflow/contrib/optimizer_v2/gradient_descent_test.py create mode 100644 tensorflow/contrib/optimizer_v2/momentum.py create mode 100644 tensorflow/contrib/optimizer_v2/momentum_test.py create mode 100644 tensorflow/contrib/optimizer_v2/optimizer_v2.py create mode 100644 tensorflow/contrib/optimizer_v2/optimizer_v2_symbols.py create mode 100644 tensorflow/contrib/optimizer_v2/optimizer_v2_test.py create mode 100644 tensorflow/contrib/optimizer_v2/rmsprop.py create mode 100644 tensorflow/contrib/optimizer_v2/rmsprop_test.py diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD index c211ad8b9b..0cebb49afb 100644 --- a/tensorflow/contrib/BUILD +++ b/tensorflow/contrib/BUILD @@ -33,6 +33,7 @@ py_library( "//tensorflow/contrib/crf:crf_py", "//tensorflow/contrib/cudnn_rnn:cudnn_rnn_py", "//tensorflow/contrib/data", + "//tensorflow/contrib/distribute:distribute", "//tensorflow/contrib/deprecated:deprecated_py", "//tensorflow/contrib/distributions:distributions_py", "//tensorflow/contrib/eager/python:tfe", @@ -74,6 +75,7 @@ py_library( "//tensorflow/contrib/nearest_neighbor:nearest_neighbor_py", "//tensorflow/contrib/nn:nn_py", "//tensorflow/contrib/opt:opt_py", + "//tensorflow/contrib/optimizer_v2:optimizer_v2_py", "//tensorflow/contrib/periodic_resample:init_py", "//tensorflow/contrib/predictor", "//tensorflow/contrib/quantization:quantization_py", diff --git a/tensorflow/contrib/__init__.py b/tensorflow/contrib/__init__.py index 4f6f539027..a8e05df708 100644 --- a/tensorflow/contrib/__init__.py +++ b/tensorflow/contrib/__init__.py @@ -30,6 +30,7 @@ from tensorflow.contrib import crf from tensorflow.contrib import cudnn_rnn from tensorflow.contrib import data from tensorflow.contrib import deprecated +from tensorflow.contrib import distribute from tensorflow.contrib import distributions from tensorflow.contrib import estimator from tensorflow.contrib import factorization @@ -84,6 +85,7 @@ from tensorflow.contrib import training from tensorflow.contrib import util from tensorflow.contrib.eager.python import tfe as eager from tensorflow.contrib.lite.python import lite +from tensorflow.contrib.optimizer_v2 import optimizer_v2_symbols as optimizer_v2 from tensorflow.contrib.receptive_field import receptive_field_api as receptive_field from tensorflow.contrib.remote_fused_graph import pylib as remote_fused_graph from tensorflow.contrib.specs import python as specs diff --git a/tensorflow/contrib/cmake/python_modules.txt b/tensorflow/contrib/cmake/python_modules.txt index cc7d791042..b10538d6d6 100644 --- a/tensorflow/contrib/cmake/python_modules.txt +++ b/tensorflow/contrib/cmake/python_modules.txt @@ -160,6 +160,8 @@ tensorflow/contrib/data/python/ops tensorflow/contrib/decision_trees tensorflow/contrib/decision_trees/proto tensorflow/contrib/deprecated +tensorflow/contrib/distribute +tensorflow/contrib/distribute/python tensorflow/contrib/distributions tensorflow/contrib/distributions/python tensorflow/contrib/distributions/python/ops @@ -342,6 +344,7 @@ tensorflow/contrib/nn/python/ops tensorflow/contrib/opt tensorflow/contrib/opt/python tensorflow/contrib/opt/python/training +tensorflow/contrib/optimizer_v2 tensorflow/contrib/pi_examples tensorflow/contrib/pi_examples/camera tensorflow/contrib/pi_examples/label_image diff --git a/tensorflow/contrib/distribute/BUILD b/tensorflow/contrib/distribute/BUILD new file mode 100644 index 0000000000..74b2cd90a1 --- /dev/null +++ b/tensorflow/contrib/distribute/BUILD @@ -0,0 +1,36 @@ +# Implementation of a prototype TF distributed computation library. + +package( + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +filegroup( + name = "all_files", + srcs = glob( + ["**/*"], + exclude = [ + "**/METADATA", + "**/OWNERS", + ], + ), + visibility = ["//tensorflow:__subpackages__"], +) + +py_library( + name = "distribute", + srcs = ["__init__.py"], + visibility = ["//tensorflow:internal"], + deps = [ + "//tensorflow/contrib/distribute/python:cross_tower_ops", + "//tensorflow/contrib/distribute/python:mirrored_strategy", + "//tensorflow/contrib/distribute/python:monitor", + "//tensorflow/contrib/distribute/python:one_device_strategy", + "//tensorflow/contrib/distribute/python:step_fn", + "//tensorflow/python:training", + "//tensorflow/python:util", + ], +) diff --git a/tensorflow/contrib/distribute/__init__.py b/tensorflow/contrib/distribute/__init__.py new file mode 100644 index 0000000000..76711baf3a --- /dev/null +++ b/tensorflow/contrib/distribute/__init__.py @@ -0,0 +1,52 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Prototype of a distributed computation library for TF.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +# pylint: disable=unused-import,wildcard-import +from tensorflow.contrib.distribute.python.cross_tower_ops import * +from tensorflow.contrib.distribute.python.mirrored_strategy import MirroredStrategy +from tensorflow.contrib.distribute.python.monitor import Monitor +from tensorflow.contrib.distribute.python.one_device_strategy import OneDeviceStrategy +from tensorflow.contrib.distribute.python.step_fn import * +from tensorflow.python.training.distribute import * + +from tensorflow.python.util.all_util import remove_undocumented + + +_allowed_symbols = [ + 'AllReduceCrossTowerOps', + 'CrossTowerOps', + 'DistributionStrategy', + 'MirroredStrategy', + 'Monitor', + 'OneDeviceStrategy', + 'ReductionToOneDeviceCrossTowerOps', + 'Step', + 'StandardInputStep', + 'StandardSingleLossStep', + 'TowerContext', + 'get_cross_tower_context', + 'get_distribution_strategy', + 'get_loss_reduction', + 'get_tower_context', + 'has_distribution_strategy', + 'require_tower_context', +] + +remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD new file mode 100644 index 0000000000..4dfd3f7228 --- /dev/null +++ b/tensorflow/contrib/distribute/python/BUILD @@ -0,0 +1,431 @@ +# Implementation of a prototype TF distributed computation library. + +package( + default_visibility = [ + "//tensorflow:internal", + ], +) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("//tensorflow:tensorflow.bzl", "py_test") +load("//tensorflow:tensorflow.bzl", "cuda_py_test") + +# TODO(priyag): Figure out testonly issues that are preventing us from +# including our tests in pip for now. + +py_library( + name = "values", + srcs = ["values.py"], + visibility = ["//tensorflow:internal"], + deps = [ + ":prefetching_ops_v2", + "//tensorflow/contrib/data/python/ops:transformation_ops", + "//tensorflow/contrib/eager/python:datasets", + "//tensorflow/python:array_ops", + "//tensorflow/python:checkpointable", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python:training", + "//tensorflow/python:util", + "//tensorflow/python/eager:context", + "@six_archive//:six", + ], +) + +cuda_py_test( + name = "values_test", + srcs = ["values_test.py"], + additional_deps = [ + ":mirrored_strategy", + ":values", + "//tensorflow/core:protos_all_py", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python:errors", + "//tensorflow/python:array_ops", + "//tensorflow/python:constant_op", + "//tensorflow/python:framework_ops", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "//tensorflow/python/estimator:model_fn", + ], +) + +py_library( + name = "mirrored_strategy", + srcs = ["mirrored_strategy.py"], + visibility = ["//tensorflow:internal"], + deps = [ + ":cross_tower_ops", + ":shared_variable_creator", + ":values", + "//tensorflow/python:array_ops", + "//tensorflow/python:device", + "//tensorflow/python:framework_ops", + "//tensorflow/python:pywrap_tensorflow", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:tape", + "@six_archive//:six", + ], +) + +py_library( + name = "one_device_strategy", + srcs = ["one_device_strategy.py"], + visibility = ["//tensorflow:internal"], + deps = [ + ":values", + "//tensorflow/contrib/eager/python:datasets", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:training", + "//tensorflow/python/eager:context", + "@six_archive//:six", + ], +) + +py_library( + name = "strategy_test_lib", + testonly = 1, + srcs = ["strategy_test_lib.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + ], + deps = [ + "//tensorflow/core:protos_all_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:constant_op", + "//tensorflow/python:framework_ops", + "//tensorflow/python:layers", + "//tensorflow/python:training", + "//tensorflow/python:variables", + "//tensorflow/python/eager:backprop", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + ], +) + +py_library( + name = "combinations", + testonly = 1, + srcs = ["combinations.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + ], + deps = [ + ":mirrored_strategy", + ":one_device_strategy", + "//tensorflow/contrib/optimizer_v2:training", + "//tensorflow/python:framework_ops", + "//tensorflow/python:training", + "//tensorflow/python:util", + "//tensorflow/python/eager:context", + "@absl_py//absl/testing:parameterized", + ], +) + +py_test( + name = "combinations_test", + srcs = ["combinations_test.py"], + tags = [ + "no_pip", + ], + deps = [ + ":combinations", + "//tensorflow/python/eager:test", + ], +) + +py_test( + name = "mirrored_strategy_test", + srcs = ["mirrored_strategy_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + ], + deps = [ + ":mirrored_strategy", + ":strategy_test_lib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + ], +) + +py_test( + name = "one_device_strategy_test", + srcs = ["one_device_strategy_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + ], + deps = [ + ":one_device_strategy", + ":strategy_test_lib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python/eager:test", + ], +) + +cuda_py_test( + name = "mirrored_strategy_multigpu_test", + srcs = ["mirrored_strategy_multigpu_test.py"], + additional_deps = [ + ":mirrored_strategy", + ":values", + ":strategy_test_lib", + "//tensorflow/core:protos_all_py", + "//tensorflow/python:constant_op", + "//tensorflow/python:layers", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + ], + tags = [ + "guitar", + "no_pip", + "multi_and_single_gpu", + # Do not perform the extra analysis on this test, because it is already + # performed for the `:mirrored_strategy_test` target. + "no_oss", + "noasan", + "notap", + "notsan", + ], +) + +py_library( + name = "step_fn", + srcs = ["step_fn.py"], + visibility = ["//tensorflow:internal"], + deps = [ + "//tensorflow/python:training", + "//tensorflow/python/eager:backprop", + ], +) + +cuda_py_test( + name = "minimize_loss_test", + srcs = ["minimize_loss_test.py"], + additional_deps = [ + ":combinations", + ":single_loss_example", + "@absl_py//absl/testing:parameterized", + "//third_party/py/numpy", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:variables", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "//tensorflow/python/ops/losses", + ], + tags = [ + "multi_and_single_gpu", + "no_pip", + ], +) + +cuda_py_test( + name = "optimizer_v2_test", + srcs = ["optimizer_v2_test.py"], + additional_deps = [ + ":combinations", + ":single_loss_example", + "@absl_py//absl/testing:parameterized", + "//third_party/py/numpy", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + ], + tags = [ + "multi_and_single_gpu", + "no_pip", + ], +) + +py_library( + name = "single_loss_example", + srcs = ["single_loss_example.py"], + deps = [ + ":step_fn", + "//tensorflow/python:array_ops", + "//tensorflow/python:constant_op", + "//tensorflow/python:layers", + "//tensorflow/python:math_ops", + "//tensorflow/python/data/ops:dataset_ops", + ], +) + +cuda_py_test( + name = "step_fn_test", + srcs = ["step_fn_test.py"], + additional_deps = [ + ":single_loss_example", + ":combinations", + "@absl_py//absl/testing:parameterized", + "//third_party/py/numpy", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + ], + tags = [ + "multi_and_single_gpu", + "no_pip", + ], +) + +py_library( + name = "monitor", + srcs = ["monitor.py"], + visibility = ["//tensorflow:internal"], + deps = [ + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + ], +) + +cuda_py_test( + name = "monitor_test", + srcs = ["monitor_test.py"], + additional_deps = [ + ":combinations", + ":monitor", + ":one_device_strategy", + ":single_loss_example", + "@absl_py//absl/testing:parameterized", + "//third_party/py/numpy", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "//tensorflow/python:framework_ops", + "//tensorflow/python:training", + ], + tags = [ + "multi_and_single_gpu", + "no_pip", + ], +) + +py_library( + name = "shared_variable_creator", + srcs = ["shared_variable_creator.py"], + visibility = ["//tensorflow:internal"], +) + +py_test( + name = "shared_variable_creator_test", + srcs = ["shared_variable_creator_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":shared_variable_creator", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:variable_scope", + "//tensorflow/python/eager:test", + ], +) + +py_binary( + name = "simple_estimator_example", + srcs = ["simple_estimator_example.py"], + deps = [ + ":mirrored_strategy", + "//tensorflow/python:array_ops", + "//tensorflow/python:constant_op", + "//tensorflow/python:layers", + "//tensorflow/python:training", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/estimator:estimator_py", + "//tensorflow/python/estimator:model_fn", + ], +) + +py_library( + name = "cross_tower_utils", + srcs = ["cross_tower_utils.py"], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/contrib/nccl:nccl_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python:math_ops", + ], +) + +py_library( + name = "cross_tower_ops", + srcs = ["cross_tower_ops.py"], + srcs_version = "PY2AND3", + deps = [ + ":cross_tower_utils", + ":values", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:training", + "//tensorflow/python/eager:context", + "@six_archive//:six", + ], +) + +py_test( + name = "cross_tower_ops_test", + srcs = ["cross_tower_ops_test.py"], + srcs_version = "PY2AND3", + tags = [ + "no_pip", + ], + deps = [ + ":combinations", + ":cross_tower_ops", + ":values", + "//tensorflow/python:array_ops", + "//tensorflow/python:constant_op", + "//tensorflow/python:framework_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "@absl_py//absl/testing:parameterized", + ], +) + +py_library( + name = "prefetching_ops_v2", + srcs = ["prefetching_ops_v2.py"], + deps = [ + "//tensorflow/contrib/data/python/ops:contrib_op_loader", + "//tensorflow/contrib/data/python/ops:prefetching_ops", + "//tensorflow/python:framework_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/util:nest", + "//tensorflow/python/data/util:sparse", + ], +) + +cuda_py_test( + name = "prefetching_ops_v2_test", + srcs = ["prefetching_ops_v2_test.py"], + additional_deps = [ + ":prefetching_ops_v2", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_ops", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + ], +) diff --git a/tensorflow/contrib/distribute/python/combinations.py b/tensorflow/contrib/distribute/python/combinations.py new file mode 100644 index 0000000000..dd8e7c4376 --- /dev/null +++ b/tensorflow/contrib/distribute/python/combinations.py @@ -0,0 +1,293 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Facilities for creating multiple test combinations. + +Here is an example of testing various optimizers in Eager and Graph mode: + +class AdditionExample(test.TestCase, parameterized.TestCase): + @combinations.generate( + combinations.combine(mode=["graph", "eager"], + optimizer=[AdamOptimizer(), + GradientDescentOptimizer()])) + def testOptimizer(self, optimizer): + ... f(optimizer)... + +This will run `testOptimizer` 4 times with the specified optimizers: 2 in +Eager and 2 in Graph mode. +The test will be provided with arguments that match the arguments of combine +by name. It is necessary to request all arguments, except for `mode`, which is +optional. + +`combine()` function is available for creating a cross product of various +options. `times()` function exists for creating a product of N `combine()`-ed +results. See below. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import OrderedDict +import sys +from absl.testing import parameterized + +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.contrib.distribute.python import one_device_strategy +from tensorflow.contrib.optimizer_v2 import adam as adam_v2 +from tensorflow.contrib.optimizer_v2 import gradient_descent as gradient_descent_v2 +from tensorflow.python.eager import context +from tensorflow.python.framework import ops +from tensorflow.python.training import adam +from tensorflow.python.training import gradient_descent +from tensorflow.python.util import tf_inspect + + +GPU_TEST = "test_gpu" in sys.argv[0] + + +def generate(combinations): + """A decorator for generating test cases of a test method or a test class. + + Args: + combinations: a list of dictionaries created using combine() and times(). + + Restrictions: + -- there should always be a "mode" argument. Accepted values are "eager" + and "graph". + -- arguments of the test method must match by name to get the corresponding + value of the combination. Tests must accept all arguments (except "mode", + which is optional). + -- distribution argument is special. It is meant for passing instances of + DistributionStrategy. Each instance is to be passed as `(, + )` tuple, where is the number of required + GPUs. If the required number of GPUs for the DistributionStrategy isn't + available then the test case is going to be skipped. + + Returns: + a decorator that will cause the test method to be run under the specified + conditions. + + Raises: + ValueError - if "mode" argument wasn't either "eager" or "graph. + """ + + def decorator(test_function): + """The decorator to be returned.""" + + # Generate good test names that can be used with --test_filter. + for combination in combinations: + # We use OrderedDicts in `combine()` and `times()` to ensure stable + # order of keys in each dictionary. + assert isinstance(combination, OrderedDict) + name = "".join([ + "_{}_{}".format( + "".join(filter(str.isalnum, key)), + "".join(filter(str.isalnum, str(value)))) + for key, value in combination.items() + ]) + combination.update({"testcase_name": "_test{}".format(name)}) + + @parameterized.named_parameters(*combinations) + def decorated(self, **kwargs): + """A wrapped test method that sets up `test_function`.""" + assert "mode" in kwargs + mode = kwargs["mode"] + + if "distribution" in kwargs: + distribution = kwargs["distribution"] + kwargs["distribution"] = distribution.strategy + if not distribution.required_gpus: + if GPU_TEST: + self.skipTest("Test that doesn't require GPUs.") + elif context.num_gpus() < distribution.required_gpus: + self.skipTest( + "{} GPUs are not available for this test. {} GPUs are available". + format(distribution.required_gpus, context.num_gpus())) + + requested_arguments = tf_inspect.getfullargspec(test_function).args + missing_arguments = set(list(kwargs.keys()) + ["self"]).difference( + set(requested_arguments + ["mode"])) + if missing_arguments: + raise ValueError("The test is missing arguments {} .".format( + missing_arguments)) + + kwargs_to_pass = {} + for arg in requested_arguments: + if arg == "self": + kwargs_to_pass[arg] = self + else: + kwargs_to_pass[arg] = kwargs[arg] + + if mode == "eager": + with context.eager_mode(), ops.Graph().as_default(): + test_function(**kwargs_to_pass) + elif mode == "graph": + with context.graph_mode(), ops.Graph().as_default(): + test_function(**kwargs_to_pass) + else: + raise ValueError( + "'mode' has to be either 'eager' or 'graph' and not {}".format( + mode)) + + return decorated + return decorator + + +def combine(**kwargs): + """Generate combinations based on its keyword arguments. + + Two sets of returned combinations can be concatenated using +. Their product + can be computed using `times()`. + + Args: + **kwargs: keyword arguments of form `option=[possibilities, ...]`. + + Returns: + a list of dictionaries for each combination. Keys in the dictionaries are + the keyword argument names. Each key has one value - one of the + corresponding keyword argument values. + """ + if not kwargs: + return [OrderedDict()] + + sort_by_key = lambda k: k[0][0] + kwargs = OrderedDict(sorted(kwargs.items(), key=sort_by_key)) + first = list(kwargs.items())[0] + + rest = dict(list(kwargs.items())[1:]) + rest_combined = combine(**rest) + + key = first[0] + values = first[1] + + return [ + OrderedDict(sorted(list(combined.items()) + [(key, v)], key=sort_by_key)) + for v in values + for combined in rest_combined + ] + + +def times(*combined): + """Generate a product of N sets of combinations. + + times(combine(a=[1,2]), combine(b=[3,4])) == combine(a=[1,2], b=[3,4]) + + Args: + *combined: N lists of dictionaries that specify combinations. + + Returns: + a list of dictionaries for each combination. + + Raises: + ValueError: if some of the inputs have overlapping keys. + """ + assert combined + + if len(combined) == 1: + return combined[0] + + first = combined[0] + rest_combined = times(*combined[1:]) + + combined_results = [] + for a in first: + for b in rest_combined: + if set(a.keys()).intersection(set(b.keys())): + raise ValueError("Keys need to not overlap: {} vs {}".format( + a.keys(), b.keys())) + + combined_results.append(OrderedDict(list(a.items()) + list(b.items()))) + return combined_results + + +class NamedObject(object): + """A class that translates an object into a good test name.""" + + def __init__(self, name, obj): + self._name = name + self._obj = obj + + def __getattr__(self, name): + return getattr(self._obj, name) + + def __call__(self, *args, **kwargs): + return self._obj(*args, **kwargs) + + def __repr__(self): + return self._name + + +class NamedDistribution(object): + """Translates DistributionStrategy and its data into a good name.""" + + def __init__(self, name, distribution, required_gpus): + self._distribution = distribution + self._name = name + self._required_gpus = required_gpus + + def __repr__(self): + return self._name + + @property + def strategy(self): + return self._distribution + + @property + def required_gpus(self): + return self._required_gpus + + +one_device_strategy = NamedDistribution( + "OneDeviceCPU", one_device_strategy.OneDeviceStrategy("/cpu:0"), + None) +mirrored_strategy_with_gpu_and_cpu = NamedDistribution( + "MirroredCPUAndGPU", + mirrored_strategy.MirroredStrategy(["/gpu:0", "/cpu:0"]), 1) +mirrored_strategy_with_two_gpus = NamedDistribution( + "Mirrored2GPUs", + mirrored_strategy.MirroredStrategy(["/gpu:0", "/gpu:1"]), 2) + +adam_optimizer_v1_fn = NamedObject( + "AdamV1", lambda: adam.AdamOptimizer(0.2, epsilon=1)) +gradient_descent_optimizer_v1_fn = NamedObject( + "GradientDescentV1", lambda: gradient_descent.GradientDescentOptimizer(0.2)) + +adam_optimizer_v2_fn = NamedObject( + "AdamV2", lambda: adam_v2.AdamOptimizer(0.2, epsilon=1)) +gradient_descent_optimizer_v2_fn = NamedObject( + "GradientDescentV2", + lambda: gradient_descent_v2.GradientDescentOptimizer(0.2)) + +graph_and_eager_modes = ["graph", "eager"] + + +def distributions_and_v1_optimizers(): + """A common set of combination with DistributionStrategies and Optimizers.""" + return combine( + distribution=[ + one_device_strategy, mirrored_strategy_with_gpu_and_cpu, + mirrored_strategy_with_two_gpus + ], + optimizer_fn=[adam_optimizer_v1_fn, gradient_descent_optimizer_v1_fn]) + + +def distributions_and_v2_optimizers(): + """DistributionStrategies and V2 Optimizers.""" + return combine( + distribution=[ + one_device_strategy, mirrored_strategy_with_gpu_and_cpu, + mirrored_strategy_with_two_gpus + ], + optimizer_fn=[adam_optimizer_v2_fn, gradient_descent_optimizer_v2_fn]) diff --git a/tensorflow/contrib/distribute/python/combinations_test.py b/tensorflow/contrib/distribute/python/combinations_test.py new file mode 100644 index 0000000000..219b24160f --- /dev/null +++ b/tensorflow/contrib/distribute/python/combinations_test.py @@ -0,0 +1,115 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for some testing utils from strategy_test_lib.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import OrderedDict + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.python.eager import test + + +class TestingCombinationsTest(test.TestCase): + + def test_combine(self): + self.assertEqual([{ + "a": 1, + "b": 2 + }, { + "a": 1, + "b": 3 + }, { + "a": 2, + "b": 2 + }, { + "a": 2, + "b": 3 + }], combinations.combine(a=[1, 2], b=[2, 3])) + + def test_add(self): + self.assertEqual( + [{ + "a": 1 + }, { + "a": 2 + }, { + "b": 2 + }, { + "b": 3 + }], + combinations.combine(a=[1, 2]) + + combinations.combine(b=[2, 3])) + + def test_times(self): + c1 = combinations.combine(mode=["graph"], loss=["callable", "tensor"]) + c2 = combinations.combine(mode=["eager"], loss=["callable"]) + c3 = combinations.combine(distribution=["d1", "d2"]) + c4 = combinations.times(c3, c1 + c2) + self.assertEqual([ + OrderedDict([("distribution", "d1"), ("loss", "callable"), + ("mode", "graph")]), + OrderedDict([("distribution", "d1"), ("loss", "tensor"), + ("mode", "graph")]), + OrderedDict([("distribution", "d1"), ("loss", "callable"), + ("mode", "eager")]), + OrderedDict([("distribution", "d2"), ("loss", "callable"), + ("mode", "graph")]), + OrderedDict([("distribution", "d2"), ("loss", "tensor"), + ("mode", "graph")]), + OrderedDict([("distribution", "d2"), ("loss", "callable"), + ("mode", "eager")]) + ], c4) + + def test_times_variable_arguments(self): + c1 = combinations.combine(mode=["graph", "eager"]) + c2 = combinations.combine(optimizer=["adam", "gd"]) + c3 = combinations.combine(distribution=["d1", "d2"]) + c4 = combinations.times(c3, c1, c2) + self.assertEqual([ + OrderedDict([("distribution", "d1"), ("mode", "graph"), + ("optimizer", "adam")]), + OrderedDict([("distribution", "d1"), ("mode", "graph"), + ("optimizer", "gd")]), + OrderedDict([("distribution", "d1"), ("mode", "eager"), + ("optimizer", "adam")]), + OrderedDict([("distribution", "d1"), ("mode", "eager"), + ("optimizer", "gd")]), + OrderedDict([("distribution", "d2"), ("mode", "graph"), + ("optimizer", "adam")]), + OrderedDict([("distribution", "d2"), ("mode", "graph"), + ("optimizer", "gd")]), + OrderedDict([("distribution", "d2"), ("mode", "eager"), + ("optimizer", "adam")]), + OrderedDict([("distribution", "d2"), ("mode", "eager"), + ("optimizer", "gd")]) + ], c4) + self.assertEqual( + combinations.combine( + mode=["graph", "eager"], + optimizer=["adam", "gd"], + distribution=["d1", "d2"]), c4) + + def test_overlapping_keys(self): + c1 = combinations.combine(mode=["graph"], loss=["callable", "tensor"]) + c2 = combinations.combine(mode=["eager"], loss=["callable"]) + with self.assertRaisesRegexp(ValueError, ".*Keys.+overlap.+"): + _ = combinations.times(c1, c2) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/cross_tower_ops.py b/tensorflow/contrib/distribute/python/cross_tower_ops.py new file mode 100644 index 0000000000..cb98351735 --- /dev/null +++ b/tensorflow/contrib/distribute/python/cross_tower_ops.py @@ -0,0 +1,410 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Classes for different algortihms of reduction and broadcasting.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import six + +from tensorflow.contrib.distribute.python import cross_tower_utils +from tensorflow.contrib.distribute.python import values as value_lib +from tensorflow.python.eager import context +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.training import device_util + + +def _validate_destinations(destinations): + if not isinstance(destinations, + (value_lib.DistributedValues, six.string_types, list)): + raise ValueError("destinations must be one of a `DistributedValues` object," + " a device string, a list of device strings or None") + + if not destinations: + raise ValueError("destinations can not be empty") + + +def _validate_value_destination_pairs(value_destination_pairs): + # pylint: disable=g-missing-docstring + if not value_destination_pairs: return False + if not isinstance(value_destination_pairs, (list, tuple)): return False + if not all([isinstance(pair, tuple) for pair in value_destination_pairs]): + return False + if not all([isinstance(v[0], value_lib.PerDevice) + for v in value_destination_pairs]): + return False + return True + + +def _get_devices_from(destinations): + if isinstance(destinations, value_lib.DistributedValues): + return list(destinations.devices) + elif isinstance(destinations, six.string_types): + return [device_util.canonicalize(destinations)] + else: + return [ + device_util.canonicalize(destination) for destination in destinations + ] + + +def _devices_match(left, right): + return set(_get_devices_from(left)) == set(_get_devices_from(right)) + + +def _all_devices_match(value_destination_pairs): + if not all([d is None or _devices_match(v, d) + for v, d in value_destination_pairs]): + return False + if not all([_devices_match(v, value_destination_pairs[0][0]) + for v, _ in value_destination_pairs[1:]]): + return False + return True + + +def _simple_broadcast(tensor, destinations): + index = {} + devices = _get_devices_from(destinations) + for d in devices: + with ops.device(d): + index[d] = array_ops.identity(tensor) + return value_lib.Mirrored(index) + + +def _simple_reduce(per_device_value, reduce_to_device, accumulation_fn, + method_string): + # pylint: disable=g-missing-docstring + all_values = [] + count = 0 + for v in per_device_value._index.values(): # pylint: disable=protected-access + if isinstance(v, value_lib.MapOutput): + v_list = v.get() + if not v_list: + continue + count += len(v_list) + # Sum within each device before aggregating across devices. + v = math_ops.add_n(v_list) + else: + count += 1 + all_values.append(v) + if not all_values: + raise ValueError("`per_device_value` must be non-empty") + + with ops.device(reduce_to_device): + with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + if method_string == "sum": + reduced = accumulation_fn(all_values) + elif method_string == "mean": + reduced = accumulation_fn(all_values) / count + else: + raise ValueError("`method_string` must be 'sum' or 'mean'") + return reduced + + +class CrossTowerOps(object): + """Base class for cross-tower reduction and broadcasting algorithms.""" + + def __init__(self): + pass + + def reduce(self, method_string, per_device_value, destinations=None): + """Reduce `per_device_value` to `destinations`. + + It runs the reduction operation defined by `method_string` and put the + result on `destinations`. + + Args: + method_string: either 'sum' or 'mean' specifying the reduction method. + per_device_value: a PerDevice object. + destinations: the reduction destinations. + + Returns: + a Mirrored object. + + Raises: + ValueError: if per_device_value is not a PerDevice object. + """ + if not isinstance(per_device_value, value_lib.PerDevice): + raise ValueError("`per_device_value` must be a `PerDevice` object.") + if destinations is not None: + _validate_destinations(destinations) + return self._reduce(method_string, per_device_value, destinations) + + def batch_reduce(self, method_string, value_destination_pairs): + """Reduce PerDevice objects in a batch. + + Reduce each first element in `value_destination_pairs` to each second + element which indicates the destinations. + + Args: + method_string: either 'sum' or 'mean' specifying the reduction method. + value_destination_pairs: a list or a tuple of tuples of PerDevice objects + and destinations. If a destionation is None, then the destinations + are set to match the devices of the input PerDevice object. + + Returns: + a list of Mirrored objects. + + Raises: + ValueError: if `value_destination_pairs` is not a list or a tuple of + tuples of PerDevice objects and destinations + """ + if not _validate_value_destination_pairs(value_destination_pairs): + raise ValueError("`value_destination_pairs` must be a list or a tuple of " + "tuples of PerDevice objects and destinations") + for _, d in value_destination_pairs: + if d is not None: + _validate_destinations(d) + + return self._batch_reduce(method_string, value_destination_pairs) + + def broadcast(self, tensor, destinations): + """Broadcast the `tensor` to destinations. + + Args: + tensor: the tensor to broadcast. + destinations: the broadcast destinations. + + Returns: + a Mirrored object. + """ + _validate_destinations(destinations) + return self._broadcast(tensor, destinations) + + def _reduce(self, method_string, per_device_value, destinations): + raise NotImplementedError( + "_reduce method must be implemented in descendants.") + + def _batch_reduce(self, method_string, value_destination_pairs): + raise NotImplementedError( + "_batch_reduce method must be implemented in descendants.") + + def _broadcast(self, tensor, destinations): + return _simple_broadcast(tensor, destinations) + + +class ReductionToOneDeviceCrossTowerOps(CrossTowerOps): + """Always do reduction to one device first and then do broadcasting. + + Batch reduction is done by reduction on each element one by one. + """ + + def __init__(self, reduce_to_device=None, accumulation_fn=math_ops.add_n): + """Constructor. + + Args: + reduce_to_device: the intermediate device to reduce to. If None, reduce + to the first device in `destinations` of the reduce() method. + accumulation_fn: a function that does accumulation. + """ + self.reduce_to_device = reduce_to_device + self.accumulation_fn = accumulation_fn + super(ReductionToOneDeviceCrossTowerOps, self).__init__() + + def _reduce(self, method_string, per_device_value, destinations): + devices = _get_devices_from(destinations or per_device_value) + reduce_to_device = self.reduce_to_device or devices[0] + reduced = _simple_reduce(per_device_value, reduce_to_device, + self.accumulation_fn, method_string) + return self.broadcast(reduced, devices) + + def _batch_reduce(self, method_string, value_destination_pairs): + return [self._reduce(method_string, t, destinations=v) + for t, v in value_destination_pairs] + + +def _group_value_by_device(per_device_values): + """Group values into sublists by their devices. + + This grouping is needed to call the allreduce library. + + Args: + per_device_values: a list of PerDevice obejcts. + + Returns: + a list of lists, each sublist has components for its corresponding device of + PerDevice objects, paired with a None. + """ + destinations = per_device_values[0].devices + grouped = [[] for _ in range(len(destinations))] + for per_device_value in per_device_values: + # pylint: disable=protected-access + for i, v in enumerate(per_device_value._index.values()): + assert per_device_value.devices == destinations + grouped[i].append((v, None)) + return grouped + + +def _ungroup_and_make_mirrored(grouped_reduced, destinations, method_string): + """Ungroup results from allreduce and make Mirrored objects. + + Each allreduce result would be divided by the number of destinations before + Mirrored objects are created if method_string is "mean". + """ + index = [{} for _ in range(len(grouped_reduced[0]))] + for d, per_device_reduced in enumerate(grouped_reduced): + for i, (v, _) in enumerate(per_device_reduced): + if method_string == "mean": + index[i][destinations[d]] = v / len(destinations) + else: + index[i][destinations[d]] = v + return [value_lib.Mirrored(v) for v in index] + + +class AllReduceCrossTowerOps(CrossTowerOps): + """Reduction using all reduce.""" + + def __init__(self, all_reduce_alg="nccl", gradient_repacking=1): + """Initialize this subclass of CrossTowerOps with allreduce. + + Gradients would be repacked for more efficient cross-device transportation. + + Args: + all_reduce_alg: the allreduce algorithm to use, currently only "nccl" or + "hierarchical_copy" are supported. + gradient_repacking: If zero, no gradient repacking would be done. If + non-zero value it specifies the number of split packs that will be + formed. + """ + self.all_reduce_alg = all_reduce_alg + self.gradient_repacking = gradient_repacking + super(AllReduceCrossTowerOps, self).__init__() + + def _reduce(self, method_string, per_device_value, destinations): + if ((destinations is None or _devices_match(per_device_value, destinations)) + and not context.executing_eagerly()): + return self._batch_all_reduce(method_string, [per_device_value])[0] + else: + devices = _get_devices_from(destinations or per_device_value) + reduce_to_device = devices[0] + reduced = _simple_reduce(per_device_value, reduce_to_device, + math_ops.add_n, method_string) + return self.broadcast(reduced, devices) + + def _batch_reduce(self, method_string, value_destination_pairs): + if (_all_devices_match(value_destination_pairs) and + not context.executing_eagerly()): + return self._batch_all_reduce(method_string, + [v[0] for v in value_destination_pairs]) + else: + if not context.executing_eagerly(): + logging.warning("Efficient batch_reduce is not supported if " + "destinations are different.") + return [ + self._reduce(method_string, t, destinations=v) + for t, v in value_destination_pairs + ] + + def _batch_all_reduce(self, method_string, per_device_values): + """All reduce algorithm in a batch.""" + logging.info("batch_all_reduce invoked for batches size = %d with algorithm" + " = %s and gradient repacking = %d", len(per_device_values), + self.all_reduce_alg, self.gradient_repacking) + destinations = per_device_values[0].devices + grouped = _group_value_by_device(per_device_values) + if self.gradient_repacking == 0: + if self.all_reduce_alg == "nccl": + reduced = cross_tower_utils.aggregate_gradients_using_nccl(grouped) + else: + # TODO(yuefengz): check that gpu ids in `destinations` are in ascending + # order. + reduced = ( + cross_tower_utils.aggregate_gradients_using_hierarchical_copy( + destinations, grouped)) + else: + device_grad_packs = [] + all_tower_shapes = [] + all_tower_sizes = [] + for tower_grads_and_vars in grouped: + with ops.colocate_with(tower_grads_and_vars[0][0]): + # Flatten all the grads. + flat_grads = [ + array_ops.reshape(g, [-1]) for g, _ in tower_grads_and_vars + ] + # Remember the original shape of all the grads. + tower_shapes = [array_ops.shape(g) for g, _ in tower_grads_and_vars] + # Remember the original sizes of all the grads. + tower_sizes = [array_ops.size(g) for g, _ in tower_grads_and_vars] + # Concat all the flat grads into a big flat tensor. + concat_grads = array_ops.concat(flat_grads, 0) + + # Split the big tensor into num_splits packs. In cases where the + # total size is not divisible num_splits, the last pack gets + # more elements. + # TODO(zhengxq): it is possible to optimize away the additional + # data movement by copying along the original variable boundary. + # TODO(zhengxq): it is also possible to optimize away all the concat + # as well. + num_splits = self.gradient_repacking + total_grad_size = array_ops.size(concat_grads) + split_size = total_grad_size // num_splits + split_size_last = total_grad_size - split_size * (num_splits - 1) + split_sizes = [split_size] * (num_splits - 1) + [split_size_last] + grad_packs = array_ops.split(concat_grads, split_sizes) + + # Ready to aggregate the repacked gradients, with fake variables. + # TODO(zhengxq): It is hacky to have to use fake variables. + # We should remove the need for variables in + # aggregate_gradients_using*. + device_grad_packs.append(zip(grad_packs, [None] * num_splits)) + all_tower_shapes.append(tower_shapes) + all_tower_sizes.append(tower_sizes) + + # The actual aggregation of the repacked gradients. Note that they are + # sharded among different aggregation trees. So it is important to + # strike the balance on num_splits. + if self.all_reduce_alg == "nccl": + summed_device_grad_packs = ( + cross_tower_utils.aggregate_gradients_using_nccl(device_grad_packs)) + else: + summed_device_grad_packs = ( + cross_tower_utils.aggregate_gradients_using_hierarchical_copy( + destinations, device_grad_packs)) + + aggregated_device_grads = [] + for (summed_tower_grad_packs, tower_grads_and_vars, tower_shapes, + tower_sizes) in zip(summed_device_grad_packs, grouped, + all_tower_shapes, all_tower_sizes): + # pylint: enable=line-too-long + # Reverse the packing operations in the previous steps. Form the + # summed gradients back into their original shapes. + with ops.colocate_with(summed_tower_grad_packs[0][0]): + # Form a list of the summed grad packs. + device_grad_packs = [g for g, _ in summed_tower_grad_packs] + + # Concat them back into a big flat tensor. + device_grads_concat = array_ops.concat(device_grad_packs, 0) + + # Split the tensors back into their original sizes. + grads_with_sizes = array_ops.split(device_grads_concat, tower_sizes) + + # Reshape the tensors back into their original shapes. + grads_with_shapes = [ + array_ops.reshape(grad, shape) + for shape, grad in zip(tower_shapes, grads_with_sizes) + ] + + # Form the list with the original list of variables. + summed_tower_grads = [ + (g, v) + for g, (_, v) in zip(grads_with_shapes, tower_grads_and_vars) + ] + aggregated_device_grads.append(summed_tower_grads) + reduced = aggregated_device_grads + return _ungroup_and_make_mirrored(reduced, per_device_values[0].devices, + method_string) diff --git a/tensorflow/contrib/distribute/python/cross_tower_ops_test.py b/tensorflow/contrib/distribute/python/cross_tower_ops_test.py new file mode 100644 index 0000000000..bb43147f5e --- /dev/null +++ b/tensorflow/contrib/distribute/python/cross_tower_ops_test.py @@ -0,0 +1,185 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for CrossTowerOps.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import itertools + +from absl.testing import parameterized + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import cross_tower_ops as cross_tower_ops_lib +from tensorflow.contrib.distribute.python import values as value_lib +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops + + +def _make_per_device(values, devices): + devices = cross_tower_ops_lib._get_devices_from(devices) + assert len(values) == len(devices) + index = {} + for d, v in zip(devices, values): + with ops.device(d): + placed_v = array_ops.identity(v) + index[d] = placed_v + return value_lib.PerDevice(index) + + +# pylint: disable=g-doc-args,g-doc-return-or-yield +def _fake_mirrored(value, devices): + """Create a faked Mirrored object for testing. + + All components of the returned Mirrored have the same objects, which is not + true in reality. + """ + devices = cross_tower_ops_lib._get_devices_from(devices) + return value_lib.Mirrored( + {d: v for d, v in zip(devices, [value] * len(devices))}) + + +_cpu_device = "/device:CPU:0" + + +class CrossTowerOpsTest(test.TestCase, parameterized.TestCase): + + def _assert_value_equal(self, left, right): + if isinstance(left, list): + for l, r in zip(left, right): + self._assert_value_equal(l, r) + else: + self.assertEqual(type(left), type(right)) + self.assertEqual(left.devices, right.devices) + if context.executing_eagerly(): + self.assertEqual([v.numpy() for v in left._index.values()], + list(right._index.values())) + else: + with self.test_session() as sess: + self.assertEqual( + sess.run(list(left._index.values())), list(right._index.values())) + + # TODO(yuefengz): decouple the num_gpus check from distribution in + # combinations module so that we can pass in devices instead of a distribution + # strategy. + reduction_to_one_combinations = combinations.combine( + cross_tower_ops=[ + combinations.NamedObject( + "DefaultReductionToOneDeviceCrossTowerOps", + cross_tower_ops_lib.ReductionToOneDeviceCrossTowerOps()), + combinations.NamedObject( + "ReductionToCPUDeviceCrossTowerOps", + cross_tower_ops_lib.ReductionToOneDeviceCrossTowerOps( + reduce_to_device=_cpu_device)), + combinations.NamedObject( + "AccumulateNCrossTowerOp", + cross_tower_ops_lib.ReductionToOneDeviceCrossTowerOps( + accumulation_fn=math_ops.accumulate_n)), + ], + distribution=[ + combinations.one_device_strategy, + combinations.mirrored_strategy_with_gpu_and_cpu, + combinations.mirrored_strategy_with_two_gpus + ], + mode=["graph", "eager"]) + allreduce_combinations = combinations.combine( + cross_tower_ops=[ + combinations.NamedObject("AllReduce", + cross_tower_ops_lib.AllReduceCrossTowerOps( + "nccl", 1)), + combinations.NamedObject("HierarchicalCopy", + cross_tower_ops_lib.AllReduceCrossTowerOps( + "hierarchical_copy", 8)), + combinations.NamedObject("AllReduceNoGradientRepacking", + cross_tower_ops_lib.AllReduceCrossTowerOps( + "nccl", 0)), + combinations.NamedObject("HierarchicalCopyNoGradientRepacking", + cross_tower_ops_lib.AllReduceCrossTowerOps( + "hierarchical_copy", 0)) + ], + distribution=[ + combinations.mirrored_strategy_with_two_gpus + ], + mode=["graph", "eager"]) + + @combinations.generate(reduction_to_one_combinations + allreduce_combinations) + def testReductionAndBroadcast(self, cross_tower_ops, distribution): + devices = distribution.worker_devices + + values = [constant_op.constant(float(d)) for d in range(len(devices))] + per_device = _make_per_device(values, devices) + mean = (len(devices) - 1.) / 2. + + values_2 = [constant_op.constant(d + 1.0) for d in range(len(devices))] + per_device_2 = _make_per_device(values_2, devices) + mean_2 = mean + 1. + + destination_mirrored = _fake_mirrored(1., devices) + destination_different = _fake_mirrored(1., _cpu_device) + destination_str = _cpu_device + destination_list = devices + + all_destinations = [ + None, destination_mirrored, destination_different, destination_str, + destination_list + ] + + # test reduce() + for destinations in all_destinations: + self._assert_value_equal( + cross_tower_ops.reduce("mean", per_device, destinations=destinations), + _fake_mirrored(mean, destinations or per_device)) + self._assert_value_equal( + cross_tower_ops.reduce( + "mean", per_device_2, destinations=destinations), + _fake_mirrored(mean_2, destinations or per_device)) + self._assert_value_equal( + cross_tower_ops.reduce("sum", per_device, destinations=destinations), + _fake_mirrored(mean * len(devices), destinations or per_device)) + self._assert_value_equal( + cross_tower_ops.reduce( + "sum", per_device_2, destinations=destinations), + _fake_mirrored(mean_2 * len(devices), destinations or per_device)) + + # test batch_reduce() + for d1, d2 in itertools.product(all_destinations, all_destinations): + self._assert_value_equal( + cross_tower_ops.batch_reduce( + "mean", [(per_device, d1), (per_device_2, d2)]), + [_fake_mirrored(mean, d1 or per_device), + _fake_mirrored(mean_2, d2 or per_device_2)]) + self._assert_value_equal( + cross_tower_ops.batch_reduce( + "sum", [(per_device, d1), (per_device_2, d2)]), + [_fake_mirrored(mean * len(devices), d1 or per_device), + _fake_mirrored(mean_2 * len(devices), d2 or per_device_2)]) + + # test broadcast() + for destinations in all_destinations: + if destinations is None: + continue + else: + self._assert_value_equal( + cross_tower_ops.broadcast(constant_op.constant(1.), destinations), + _fake_mirrored(1., destinations)) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/cross_tower_utils.py b/tensorflow/contrib/distribute/python/cross_tower_utils.py new file mode 100644 index 0000000000..93acd835d7 --- /dev/null +++ b/tensorflow/contrib/distribute/python/cross_tower_utils.py @@ -0,0 +1,153 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Utilities for cross_tower_ops.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib import nccl +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops + + +def aggregate_gradients_using_nccl(tower_grads): + """Aggregate gradients using nccl allreduce.""" + agg_all_g_and_v = [] + for single_g_and_v in zip(*tower_grads): + single_grads = [g for g, _ in single_g_and_v] + agg_grads = nccl.all_sum(single_grads) + agg_all_g_and_v.append( + [(g, v) for g, (_, v) in zip(agg_grads, single_g_and_v)]) + + agg_all_g_and_v = list(zip(*agg_all_g_and_v)) + + return agg_all_g_and_v + + +def aggregate_gradients_using_hierarchical_copy(avail_devices, tower_grads): + """Aggregate gradients using hierarchical copies. + + Args: + avail_devices: available GPU devices. + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over towers. The inner list is over individual gradients. + + Returns: + The list of (aggregated_gradient, variable), where the gradient has been + summed across all towers and the variable is chosen from the first tower. + """ + # This only works for DGX-1 type of machine topology + # Device peer to peer matrix + # DMA: 0 1 2 3 4 5 6 7 + # 0: Y Y Y Y Y N N N + # 1: Y Y Y Y N Y N N + # 2: Y Y Y Y N N Y N + # 3: Y Y Y Y N N N Y + # 4: Y N N N Y Y Y Y + # 5: N Y N N Y Y Y Y + # 6: N N Y N Y Y Y Y + # 7: N N N Y Y Y Y Y + agg_grads = [] + num_devices = len(avail_devices) + # In the special case of DGX-1 machine topology, the two groups have equal + # size. + group_size = num_devices // 2 + for i, single_grads in enumerate(zip(*tower_grads)): + group_0_main_device = i % num_devices + group_1_main_device = (group_0_main_device + group_size) % num_devices + if group_0_main_device < group_size: + group_0_begin = 0 + group_1_begin = group_size + else: + group_0_begin = group_size + group_1_begin = 0 + + # Aggregate the first group. + group_0_device_grads = single_grads[group_0_begin: + group_0_begin + group_size] + with ops.device(avail_devices[group_0_main_device]): + group_0_agg_grads, _ = aggregate_single_gradient_using_copy( + group_0_device_grads, False, False) + + # Aggregate the second group. + group_1_device_grads = single_grads[group_1_begin: + group_1_begin + group_size] + with ops.device(avail_devices[group_1_main_device]): + group_1_agg_grads, _ = aggregate_single_gradient_using_copy( + group_1_device_grads, False, False) + + # Aggregate between the groups. + with ops.device(avail_devices[group_0_main_device]): + (agg_total_grads, _), _ = aggregate_single_gradient_using_copy( + [group_0_agg_grads, group_1_agg_grads], False, False) + + # Broadcast the result back into the root of each group. + with ops.device(avail_devices[group_0_main_device]): + group_0_agg_grads_bcast = array_ops.identity(agg_total_grads) + with ops.device(avail_devices[group_1_main_device]): + group_1_agg_grads_bcast = array_ops.identity(agg_total_grads) + + agg_grads_bcast = [] + for j in range(len(single_grads)): + with ops.device(avail_devices[j]): + # Broadcast the result back to each member in the group from the root. + if (group_0_main_device < group_size) == (j < group_size): + src_device_grad = group_0_agg_grads_bcast + else: + src_device_grad = group_1_agg_grads_bcast + agg_grads_bcast.append(array_ops.identity(src_device_grad)) + + agg_grads.append( + [(g, v) for g, (_, v) in zip(agg_grads_bcast, single_grads)]) + + agg_grads = list(zip(*agg_grads)) + + return agg_grads + + +def aggregate_single_gradient_using_copy(grad_and_vars, use_mean, + check_inf_nan): + """Calculate the average gradient for a shared variable across all towers. + + Note that this function provides a synchronization point across all towers. + + Args: + grad_and_vars: A list or tuple of (gradient, variable) tuples. Each + (gradient, variable) pair within the outer list represents the gradient + of the variable calculated for a single tower, and the number of pairs + equals the number of towers. + use_mean: if True, mean is taken, else sum of gradients is taken. + check_inf_nan: check grads for nans and infs. + + Returns: + The tuple ([(average_gradient, variable),], has_nan_or_inf) where the + gradient has been averaged across all towers. The variable is chosen from + the first tower. The has_nan_or_inf indicates the grads has nan or inf. + """ + grads = [g for g, _ in grad_and_vars] + grad = math_ops.add_n(grads) + + if use_mean and len(grads) > 1: + grad = array_ops.multiply(grad, 1.0 / len(grads)) + + v = grad_and_vars[0][1] + if check_inf_nan: + has_nan_or_inf = array_ops.logical_not( + array_ops.reduce_all(array_ops.is_finite(grads))) + return (grad, v), has_nan_or_inf + else: + return (grad, v), None diff --git a/tensorflow/contrib/distribute/python/minimize_loss_test.py b/tensorflow/contrib/distribute/python/minimize_loss_test.py new file mode 100644 index 0000000000..0fa90df79b --- /dev/null +++ b/tensorflow/contrib/distribute/python/minimize_loss_test.py @@ -0,0 +1,279 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for running legacy optimizer code with DistributionStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.contrib.distribute.python.single_loss_example import batchnorm_example +from tensorflow.contrib.distribute.python.single_loss_example import minimize_loss_example +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables as variables_lib +from tensorflow.python.ops.losses import losses_impl + + +class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): + + @combinations.generate( + combinations.times( + combinations.distributions_and_v1_optimizers(), + combinations.combine(mode=["graph"], use_callable_loss=[True, False]) + + combinations.combine(mode=["eager"], use_callable_loss=[True]))) + def testTrainNetwork(self, distribution, optimizer_fn, + use_callable_loss=True): + with distribution.scope(): + model_fn, dataset, layer = minimize_loss_example( + optimizer_fn, + use_bias=True, + use_callable_loss=use_callable_loss) + + iterator = distribution.distribute_dataset(dataset) + + def run_step(): + return distribution.group( + distribution.call_for_each_tower( + model_fn, iterator.get_next(), run_concurrently=layer.built)) + + if not context.executing_eagerly(): + with self.test_session() as sess: + run_step = sess.make_callable(run_step()) + self.evaluate(variables_lib.global_variables_initializer()) + + weights, biases = [], [] + for _ in range(10): + run_step() + + weights.append(self.evaluate(distribution.fetch(layer.kernel))) + biases.append(self.evaluate(distribution.fetch(layer.bias))) + + error = abs(numpy.add(numpy.squeeze(weights), numpy.squeeze(biases)) - 1) + is_not_increasing = all(y <= x for x, y in zip(error, error[1:])) + self.assertTrue(is_not_increasing) + + @combinations.generate( + combinations.times( + combinations.distributions_and_v1_optimizers() + + combinations.distributions_and_v2_optimizers(), + combinations.combine(mode=["graph", "eager"]))) + def testOptimizerInsideModelFn(self, distribution, optimizer_fn): + created_variables = [] + trainable_variables = [] + + def appending_creator(next_creator, *args, **kwargs): + v = next_creator(*args, **kwargs) + created_variables.append(v.name) + if "trainable" in kwargs and kwargs["trainable"]: + trainable_variables.append(v.name) + return v + + # Creator scope needs to be set before it's used inside + # `distribution.scope`. + with variable_scope.variable_creator_scope( + appending_creator), distribution.scope(): + model_fn, dataset, layer = minimize_loss_example( + optimizer_fn, + use_bias=True, + use_callable_loss=True, + create_optimizer_inside_model_fn=True) + + iterator = distribution.distribute_dataset(dataset) + + def run_step(): + return distribution.group( + distribution.call_for_each_tower( + model_fn, iterator.get_next(), run_concurrently=layer.built)) + + if not context.executing_eagerly(): + with self.test_session() as sess: + run_step = sess.make_callable(run_step()) + self.evaluate(variables_lib.global_variables_initializer()) + + run_step() + + def get_expected_variables(optimizer_fn, num_parameter_devices): + variables_map = { + "GradientDescent": ["dense/kernel", "dense/bias"], + "Adam": [ + "dense/kernel", "dense/bias", "beta1_power", "beta2_power", + "dense/kernel/Adam", "dense/kernel/Adam_1", "dense/bias/Adam", + "dense/bias/Adam_1" + ] + } + variables = variables_map[optimizer_fn().get_name()] + variables.extend([ + v + "/replica_{}".format(replica) + for v in variables + for replica in range(1, num_parameter_devices) + ]) + return set([v + ":0" for v in variables]) + + self.assertEqual( + get_expected_variables(optimizer_fn, + len(distribution.parameter_devices)), + set(created_variables)) + + @combinations.generate( + combinations.times(combinations.distributions_and_v1_optimizers(), + combinations.combine( + mode=["graph", "eager"], + momentum=[0.8, 0.9, 0.99], + renorm=[False, True]))) + def testTrainNetworkWithBatchNorm(self, distribution, optimizer_fn, momentum, + renorm): + """Verifies that moving mean updates are reduced across towers.""" + with distribution.scope(): + num_towers = len(distribution.worker_devices) + model_fn, dataset, batchnorm = batchnorm_example( + optimizer_fn, + batch_per_epoch=num_towers, + momentum=momentum, + renorm=renorm) + + # Disable prefetching since that makes the specific input on each device + # to be non deterministic, and this test relies on specific input being + # on each device. + if isinstance(distribution, mirrored_strategy.MirroredStrategy): + distribution._prefetch_on_device = False + iterator = distribution.distribute_dataset(dataset) + + def run_step(): + return control_flow_ops.group( + distribution.unwrap( + distribution.call_for_each_tower( + model_fn, + iterator.get_next(), + run_concurrently=batchnorm.built)) + + ops.get_collection(ops.GraphKeys.UPDATE_OPS)) + + if not context.executing_eagerly(): + with self.test_session() as sess: + run_step = sess.make_callable(run_step()) + self.evaluate(variables_lib.global_variables_initializer()) + + expected_moving_means = [0.] * 8 + + def averaged_batch_mean(i): + # Each batch has shape [16, 8] where the ith element in jth list is + # (8 * j + i + tower_id * 100). So the batch mean in each tower is + # (60 + i + tower_id * 100). So here comes its batch mean over all + # towers: + return 60. + i + (num_towers - 1.) / 2. * 100. + + for _ in range(10): + run_step() + moving_means = self.evaluate(distribution.fetch(batchnorm.moving_mean)) + + # We make sure that the moving_mean is updated as if the sample mean is + # calculated over all towers. + for i, expected_moving_mean in enumerate(expected_moving_means): + expected_moving_means[i] -= (( + expected_moving_mean - averaged_batch_mean(i)) * (1.0 - momentum)) + self.assertNear(expected_moving_means[i], moving_means[i], 0.0001) + + @combinations.generate( + combinations.times( + combinations.combine( + distribution=[combinations.one_device_strategy, + combinations.mirrored_strategy_with_gpu_and_cpu, + combinations.mirrored_strategy_with_two_gpus], + optimizer_fn=[combinations.gradient_descent_optimizer_v1_fn, + combinations.gradient_descent_optimizer_v2_fn], + loss_reduction=[losses_impl.Reduction.SUM, + losses_impl.Reduction.MEAN, + losses_impl.Reduction.SUM_OVER_BATCH_SIZE, + losses_impl.Reduction.SUM_OVER_NONZERO_WEIGHTS]), + combinations.combine(mode=["graph"], use_callable_loss=[True, False]) + + combinations.combine(mode=["eager"], use_callable_loss=[True]))) + def testMeanVsSum(self, distribution, optimizer_fn, loss_reduction, + use_callable_loss): + with distribution.scope(): + all_vars = [] + + def model_fn(x, y): + + def loss_fn(): + # Use fixed initialization to make the steps deterministic. + w = variable_scope.get_variable("w", initializer=[[2.]]) + all_vars.append(w) + predict = math_ops.matmul(x, w) + return losses_impl.mean_squared_error( + y, predict, reduction=loss_reduction) + + optimizer = optimizer_fn() # GradientDescent with 0.2 learning rate + + if use_callable_loss: + return optimizer.minimize(loss_fn) + else: + return optimizer.minimize(loss_fn()) + + features = dataset_ops.Dataset.from_tensors([[2.], [7.]]) + labels = dataset_ops.Dataset.from_tensors([[6.], [21.]]) + dataset = dataset_ops.Dataset.zip((features, labels)).repeat() + iterator = distribution.distribute_dataset(dataset) + + def run_step(): + return distribution.group( + distribution.call_for_each_tower( + model_fn, *iterator.get_next(), run_concurrently=False)) + + if not context.executing_eagerly(): + with self.test_session() as sess: + run_step = sess.make_callable(run_step()) + self.evaluate(variables_lib.global_variables_initializer()) + + run_step() + + self.assertEqual(distribution.num_towers, len(all_vars)) + v = all_vars[0] + self.assertTrue(all([v is vi for vi in all_vars[1:]])) + weight = numpy.squeeze(self.evaluate(distribution.fetch(v))) + # Our model is: + # predict = x * w + # loss = (predict - y)^2 + # dloss/dpredict = 2*(predict - y) + # dloss/dw = 2 * x^T @ (predict - y) + # For our batch size of 2, assuming sum loss reduction: + # x = [2, 7] + # y = [6, 21] + # w_initial = 2 + # predict = [4, 14] + # predict - y = [-2, -7] + # dloss/dw = 2 <[2, 7], [-2, -7]> = - 2(4 + 49) = -106 + # So unreplicated the update to w with lr=0.2 is -0.2 * -106 = 21.2 + # with sum loss reduction, or 10.6 with mean. + if loss_reduction == losses_impl.Reduction.SUM: + # Note that the "distribution.num_towers" factor will go away once + # we split the input across towers, instead of pulling a complete + # batch of input per tower. + self.assertNear(weight, 2 + 21.2 * distribution.num_towers, 0.0001) + else: + # One of the mean loss reductions. + self.assertNear(weight, 2 + 10.6, 0.0001) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py new file mode 100644 index 0000000000..8cf83c52d8 --- /dev/null +++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py @@ -0,0 +1,486 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Class MirroredStrategy implementing DistributionStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import threading +import six + +from tensorflow.contrib.distribute.python import cross_tower_ops as cross_tower_ops_lib +from tensorflow.contrib.distribute.python import shared_variable_creator +from tensorflow.contrib.distribute.python import values +from tensorflow.python import pywrap_tensorflow +from tensorflow.python.eager import context +from tensorflow.python.eager import tape +from tensorflow.python.framework import device as tf_device +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.training import coordinator +from tensorflow.python.training import device_util +from tensorflow.python.training import distribute as distribute_lib + + +# TODO(josh11b): Replace asserts in this file with if ...: raise ... + + +def _cpu_device(device): + cpu_device = tf_device.DeviceSpec.from_string(device) + cpu_device.merge_from(tf_device.DeviceSpec(device_type="CPU", device_index=0)) + return cpu_device.to_string() + + +class _RequestedStop(Exception): + pass + + +class MirroredStrategy(distribute_lib.DistributionStrategy): + """Mirrors vars to distribute across multiple devices on a single machine. + + This strategy uses one tower per device and sync replication. + """ + + def __init__(self, + devices=None, + num_gpus=None, + cross_tower_ops=None, + prefetch_on_device=None): + super(MirroredStrategy, self).__init__() + # Convert `num_gpus` into `devices`, shouldn't specify both. + if devices is None: + if num_gpus is None: + num_gpus = context.num_gpus() + devices = ["/device:GPU:%d" % d for d in range(num_gpus)] + elif num_gpus is not None: + raise ValueError("Must only specify one of `devices` and `num_gpus`.") + + assert devices, "Must specify at least one device." + assert len(set(devices)) == len(devices), ( + "No duplicates allowed in `devices` argument.") + # TODO(josh11b): Require at least 2 devices? + self._devices = devices + self._canonical_device_set = set( + [device_util.canonicalize(d) for d in devices]) + self._device_index = values.PerDevice( + dict((d, i) for i, d in enumerate(devices))) + self.cross_tower_ops = ( + cross_tower_ops or + cross_tower_ops_lib.ReductionToOneDeviceCrossTowerOps()) + self._prefetch_on_device = prefetch_on_device + + def _create_variable(self, next_creator, *args, **kwargs): + """Create a mirrored variable. See `DistributionStrategy.scope`.""" + # Figure out what collections this variable should be added to. + # We'll add the MirroredVariable to those collections instead. + collections = kwargs.pop("collections", None) + if collections is None: + collections = [ops.GraphKeys.GLOBAL_VARIABLES] + kwargs["collections"] = [] + + colocate_with = kwargs.pop("colocate_with", None) + devices = self._get_devices_from(colocate_with) + + tower_local = kwargs.pop("tower_local_reduce_method", None) + if tower_local is not None: + kwargs["trainable"] = False + + # TODO(josh11b,apassos): It would be better if variable initialization + # was never recorded on the tape instead of having to do this manually + # here. + with tape.stop_recording(): + index = {} + for i, d in enumerate(devices): + with ops.device(d): + if i > 0: + # Give replicas meaningful distinct names: + var0name = index[devices[0]].name.split(":")[0] + kwargs["name"] = "%s/replica_%d" % (var0name, i) + # Initialize replicas with the same value: + if context.executing_eagerly(): + initial_value = index[devices[0]].value() + else: + initial_value = index[devices[0]].initial_value + kwargs["initial_value"] = array_ops.identity(initial_value) + with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + v = next_creator(*args, **kwargs) + assert not isinstance(v, values.DistributedVariable) + index[d] = v + + if tower_local is None: + result = values.MirroredVariable(index, index[devices[0]]) + else: + result = values.TowerLocalVariable( + index, index[devices[0]], tower_local) + + if not context.executing_eagerly(): + g = ops.get_default_graph() + # If "trainable" is True, next_creator() will add the member variables + # to the TRAINABLE_VARIABLES collection, so we manually remove + # them and replace with the MirroredVariable. We can't set + # "trainable" to False for next_creator() since that causes functions + # like implicit_gradients to skip those variables. + if kwargs.get("trainable", True): + collections.append(ops.GraphKeys.TRAINABLE_VARIABLES) + l = g.get_collection_ref(ops.GraphKeys.TRAINABLE_VARIABLES) + for v in index.values(): + l.remove(v) + g.add_to_collections(collections, result) + return result + + def distribute_dataset(self, dataset): + per_device_dataset = values.PerDeviceDataset( + dataset, self._devices, self._prefetch_on_device) + return per_device_dataset.make_one_shot_iterator() + + def _broadcast(self, tensor, destinations): + # TODO(josh11b): In eager mode, use one thread per device, or async mode. + return self.cross_tower_ops.broadcast(tensor, destinations or self._devices) + + def _call_for_each_tower(self, fn, *args, **kwargs): + """Run `fn` in separate threads, once per tower/worker device. + + Args: + fn: function to run (will be run once per device, each in its own thread). + *args: positional arguments for `fn` + **kwargs: keyword arguments for `fn`. + `"run_concurrently"`: Boolean indicating whether executions of `fn` + can be run concurrently (under eager execution only), defaults to + `True`. + + Returns: + Merged return value of `fn` across all towers. + + Raises: + RuntimeError: If fn() calls get_tower_context().merge_call() a different + number of times for when called for different devices. + """ + run_concurrently = kwargs.pop("run_concurrently", True) + if not context.executing_eagerly(): + # Lots of TF library code isn't thread-safe in graph mode, and + # there is little to be gained by turning on multithreading when + # constructing a graph. + run_concurrently = False + # Needed for per-thread device, etc. contexts in graph mode. + ops.get_default_graph().switch_to_thread_local() + elif run_concurrently is None: + run_concurrently = True + + coord = coordinator.Coordinator( + clean_stop_exception_types=(_RequestedStop,)) + + shared_variable_store = {} + + # TODO(isaprykin): Create these threads once instead of during every run() + # call. + threads = [] + for index, d in enumerate(self._devices): + variable_creator_fn = shared_variable_creator.make_fn( + shared_variable_store, index) + t = MirroredStrategy._MirroredTowerThread( + self, coord, d, variable_creator_fn, fn, + *values.select_device(d, args), **values.select_device(d, kwargs)) + threads.append(t) + + for t in threads: + t.start() + + # When `fn` starts `should_run` event is set on _MirroredTowerThread + # (`MTT`) threads. The execution waits until + # `MTT.has_paused` is set, which indicates that either `fn` is + # complete or a `get_tower_context().merge_call()` is called. If `fn` is + # complete, then `MTT.done` is set to True. Otherwise, arguments + # of `get_tower_context().merge_call` from all paused threads are grouped + # and the `merge_fn` is performed. Results of the + # `get_tower_context().merge_call` are then set to `MTT.merge_result`. + # Each such `get_tower_context().merge_call` call returns the + # `MTT.merge_result` for that thread when `MTT.should_run` event + # is reset again. Execution of `fn` resumes. + + try: + with coord.stop_on_exception(): + all_done = False + while not all_done and not coord.should_stop(): + done = [] + if run_concurrently: + for t in threads: + t.should_run.set() + for t in threads: + t.has_paused.wait() + t.has_paused.clear() + if coord.should_stop(): + return None + done.append(t.done) + else: + for t in threads: + t.should_run.set() + t.has_paused.wait() + t.has_paused.clear() + if coord.should_stop(): + return None + done.append(t.done) + if coord.should_stop(): + return None + all_done = all(done) + if not all_done: + if any(done): + raise RuntimeError("Some towers made a different number of " + "tower_context().merge_call() calls.") + # get_tower_context().merge_call() case + merge_args = values.regroup( + {t.device: t.merge_args for t in threads}) + merge_kwargs = values.regroup( + {t.device: t.merge_kwargs for t in threads}) + merge_result = threads[0].merge_fn( + self, *merge_args, **merge_kwargs) + for t in threads: + t.merge_result = values.select_device(t.device, merge_result) + finally: + for t in threads: + t.should_run.set() + coord.join(threads) + + return values.regroup({t.device: t.main_result for t in threads}) + + def map(self, map_over, fn, *args, **kwargs): + # TODO(josh11b): In eager mode, use one thread per device. + index = {} + i = 0 + for m in map_over: + d = self._devices[i % len(self._devices)] + with ops.device(d): + l = index.get(d, []) + l.append(fn(m, + *values.select_device_mirrored(d, args), + **values.select_device_mirrored(d, kwargs))) + index[d] = l + # TODO(josh11b): Need a values.regroup equivalent that handles MapOutput + # in addition to PerDevice data. + return values.PerDevice({k: values.MapOutput(v) for k, v in index.items()}) + + def _reduce(self, method_string, value, destinations): + if len(self._devices) == 1 and not isinstance(value, values.PerDevice): + value = values.PerDevice({self._devices[0]: value}) + assert isinstance(value, values.PerDevice) + return self.cross_tower_ops.reduce( + method_string, value, destinations=destinations) + + def _batch_reduce(self, method_string, value_destination_pairs): + return self.cross_tower_ops.batch_reduce(method_string, + value_destination_pairs) + + def _update(self, var, fn, *args, **kwargs): + # TODO(josh11b): Also support TowerLocalVariables here? If so, args and + # kwargs don't need to be mirrored. + assert isinstance(var, values.MirroredVariable) + # TODO(josh11b): In eager mode, use one thread per device. + updates = {} + for d, v in var._index.items(): # pylint: disable=protected-access + name = "update_%d" % self._device_index.get(d) + with ops.device(d), distribute_lib.UpdateContext(d), ops.name_scope(name): + updates[d] = fn(v, + *values.select_device_mirrored(d, args), + **values.select_device_mirrored(d, kwargs)) + return values.regroup(updates, values.Mirrored) + + def _update_non_slot(self, colocate_with, fn, *args, **kwargs): + assert isinstance(colocate_with, list) + # TODO(josh11b): In eager mode, use one thread per device. + updates = {} + for d in colocate_with: + name = "update_%d" % self._device_index.get(d) + with ops.device(d), distribute_lib.UpdateContext(d), ops.name_scope(name): + updates[d] = fn(*values.select_device_mirrored(d, args), + **values.select_device_mirrored(d, kwargs)) + return values.regroup(updates, values.Mirrored) + + def _fetch(self, val, destination, fn): + """Return a copy of `val` or `fn(val)` on `destination`.""" + assert isinstance(destination, six.string_types) + if isinstance(val, values.TowerLocalVariable): + val = self.reduce(val.reduce_method, val, destinations=destination) + with ops.device(destination): + return fn(self.unwrap(val)[0]) + + assert isinstance(val, values.Mirrored), ( + "val = %s (type %s)" % (val, val.__class__.__name__)) + if val.on_device(destination): + with ops.device(destination): + # Use an identity here to make sure we are returning a tensor + # instead of e.g. a variable object. + return array_ops.identity(fn(val.get(destination))) + device = None + for d in self._devices: + if val.on_device(d): + device = d + break + assert device is not None, ( + "Could not find destination %s in list of devices %s." % + (destination, val.devices)) + with ops.device(device): + v = fn(val.get(device)) + with ops.device(destination): + return array_ops.identity(v) + + def _unwrap(self, val): + if isinstance(val, values.DistributedValues): + # Return in a deterministic order. + if set(val.devices) == self._canonical_device_set: + return [val.get(device=d) for d in self._devices] + return [val.get(device=d) for d in sorted(val.devices)] + return [val] + + @property + def is_single_tower(self): + return len(self._devices) == 1 + + @property + def num_towers(self): + return len(self._devices) + + def _worker_device_index(self): + return self._device_index + + @property + def worker_devices(self): + # Make a copy to prevent users from accidentally mutating our copy. + return list(self._devices) + + @property + def parameter_devices(self): + return list(self._devices) + + def non_slot_devices(self, var_list): + del var_list + return list(self._devices) + + def _get_devices_from(self, colocate_with=None): + if colocate_with is None: + return self._devices + elif isinstance(colocate_with, values.DistributedValues): + # pylint: disable=protected-access + return list(colocate_with._index.keys()) + elif isinstance(colocate_with, six.string_types): + return [colocate_with] + else: + return colocate_with + + class _MirroredTowerThread(threading.Thread): + """A thread that runs() a function on a device.""" + + def __init__(self, dist, coord, device, variable_creator_fn, fn, *args, + **kwargs): + super(MirroredStrategy._MirroredTowerThread, self).__init__() # pylint: disable=protected-access + self.coord = coord + self.distribution = dist + self.device = device + self.tower_id = dist.worker_devices.index(device) + self.variable_creator_fn = variable_creator_fn + # State needed to run and return the results of `fn`. + self.main_fn = fn + self.main_args = args + self.main_kwargs = kwargs + self.main_result = None + self.done = False + # State needed to run the next merge_call() (if any) requested via + # TowerContext. + self.merge_fn = None + self.merge_args = None + self.merge_kwargs = None + self.merge_result = None + # We use a thread.Event for the main thread to signal when this + # thread should start running (`should_run`), and another for + # this thread to transfer control back to the main thread + # (`has_paused`, either when it gets to a + # `get_tower_context().merge_call` or when `fn` returns). In + # either case the event starts cleared, is signaled by calling + # set(). The receiving thread waits for the signal by calling + # wait() and then immediately clearing the event using clear(). + self.should_run = threading.Event() + self.has_paused = threading.Event() + # These fields have to do with inheriting various contexts from the + # parent thread: + # pylint: disable=protected-access + self.context_mode = context.context()._eager_context.mode + if not context.context()._context_handle: + context.context()._initialize_handle_and_devices() + self.context_device_policy = ( + pywrap_tensorflow.TFE_ContextGetDevicePlacementPolicy( + context.context()._context_handle)) + self.graph = ops.get_default_graph() + self._variable_creator_stack = self.graph._variable_creator_stack[:] + self._captured_var_scope = variable_scope.get_variable_scope() + # Adding a "/" at end lets us re-enter this scope later. + self._captured_name_scope = self.graph.get_name_scope() + if self._captured_name_scope: + self._captured_name_scope += "/" + if self.tower_id > 0: + if not self._captured_name_scope: + self._captured_name_scope = "" + self._captured_name_scope += "tower_%d/" % self.tower_id + + def run(self): + # pylint: disable=protected-access + self.graph._variable_creator_stack = self._variable_creator_stack + self.should_run.wait() + self.should_run.clear() + try: + if self.coord.should_stop(): + return + with self.coord.stop_on_exception(), \ + context.context()._mode(self.context_mode), \ + context.context().device_policy(self.context_device_policy), \ + self.graph.as_default(), \ + MirroredTowerContext(self.distribution, self.tower_id), \ + ops.device(self.device), \ + ops.name_scope(self._captured_name_scope), \ + variable_scope.variable_scope( + self._captured_var_scope, reuse=self.tower_id > 0), \ + variable_scope.variable_creator_scope(self.variable_creator_fn): + self.main_result = self.main_fn(*self.main_args, **self.main_kwargs) + self.done = True + finally: + self.has_paused.set() + + +class MirroredTowerContext(distribute_lib.TowerContext): + """TowerContext used in MirroredStrategy.call_for_each_tower(). + + Opened in `_MirroredTowerThread`, to allow the user to invoke + `MirroredStrategy`'s specific implementation of `merge_call()`, + which works by delegating the function and its arguments to + the main thread (the one that invoked + `MirroredStrategy.call_for_each_tower()`). + """ + + def _merge_call(self, fn, *args, **kwargs): + """Delegate to the main thread to actually perform merge_call().""" + t = threading.current_thread() # a _MirroredTowerThread + t.merge_fn = fn + t.merge_args = args + t.merge_kwargs = kwargs + t.has_paused.set() + t.should_run.wait() + t.should_run.clear() + if t.coord.should_stop(): + raise _RequestedStop() + return t.merge_result + + @property + def device(self): + distribute_lib.require_tower_context(self) + return self._distribution_strategy.worker_devices[self._tower_id] diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py new file mode 100644 index 0000000000..9e9f06da8e --- /dev/null +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -0,0 +1,435 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Multi-GPU tests for MirroredStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys + +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.contrib.distribute.python import strategy_test_lib +from tensorflow.contrib.distribute.python import values +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.layers import core +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.training import distribute as distribute_lib + +GPU_TEST = "test_gpu" in sys.argv[0] + + +class MirroredTwoDeviceDistributionTest(strategy_test_lib.DistributionTestBase): + + def _get_distribution_strategy(self): + devices = ["/device:CPU:0", "/device:GPU:0"] + if GPU_TEST: + self.assertGreater(context.num_gpus(), 0) + if context.num_gpus() > 1: + devices = ["/device:GPU:0", "/device:GPU:1"] + print(self.id().split(".")[-1], "devices:", ", ".join(devices)) + return mirrored_strategy.MirroredStrategy(devices) + + def testMinimizeLossEager(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self._test_minimize_loss_eager(self._get_distribution_strategy()) + + def testMinimizeLossGraph(self): + soft_placement = not GPU_TEST + print("testMinimizeLossGraph soft_placement:", soft_placement) + self._test_minimize_loss_graph( + self._get_distribution_strategy(), soft_placement=soft_placement) + + def testMapReduce(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self._test_map_reduce(self._get_distribution_strategy()) + + def testDeviceIndex(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self._test_device_index(self._get_distribution_strategy()) + + def testTowerId(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self._test_tower_id(self._get_distribution_strategy()) + + def testNumTowers(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self.assertEqual(2, self._get_distribution_strategy().num_towers) + + @test_util.run_in_graph_and_eager_modes() + def testCallAndMergeExceptions(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self._test_call_and_merge_exceptions(self._get_distribution_strategy()) + + @test_util.run_in_graph_and_eager_modes() + def testRunRegroupError(self): + + def run_fn(device_id): + # Generates a list with different lengths on different devices. + # Will fail in _regroup() (if more than one device). + return list(range(device_id)) + + dist = self._get_distribution_strategy() + with dist.scope(), self.assertRaises(AssertionError): + dist.call_for_each_tower(run_fn, dist.worker_device_index) + + @test_util.run_in_graph_and_eager_modes() + def testReduceToCpu(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + + def run_fn(device_id): + return device_id + + dist = self._get_distribution_strategy() + with dist.scope(): + result = dist.call_for_each_tower(run_fn, dist.worker_device_index) + reduced = dist.reduce("sum", result, destinations="/device:CPU:0") + unwrapped = dist.unwrap(reduced) + self.assertEqual(1, len(unwrapped)) + expected = sum(range(len(dist.worker_devices))) + self.assertEqual(expected, self.evaluate(unwrapped[0])) + + +@test_util.with_c_api +class MirroredStrategyVariableCreationTest(test.TestCase): + + config = config_pb2.ConfigProto() + config.allow_soft_placement = True + + def _skip_eager_if_gpus_less_than(self, num_gpus): + if context.num_gpus() < num_gpus and context.executing_eagerly(): + self.skipTest("Enough GPUs not available for this test in eager mode.") + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSingleVariable(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(): + # This variable should be created only once across the threads because of + # special variable_creator functions used by `dist.call_for_each_tower`. + v = variable_scope.variable(1.0, name="foo") + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertIsInstance(result, values.MirroredVariable) + self.assertEquals("foo:0", result.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testUnnamedVariable(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(): + v = variable_scope.variable(1.0) + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertIsInstance(result, values.MirroredVariable) + # Default name of "Variable" will be used. + self.assertEquals("Variable:0", result.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testMultipleVariables(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(): + vs = [] + for i in range(5): + vs.append(variable_scope.variable(1.0, name="foo" + str(i))) + distribute_lib.get_tower_context().merge_call(lambda _: _) + return vs + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + for i, v in enumerate(result): + self.assertIsInstance(v, values.MirroredVariable) + self.assertEquals("foo" + str(i) + ":0", v.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testMultipleVariablesWithSameCanonicalName(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(): + vs = [] + vs.append(variable_scope.variable(1.0, name="foo/bar")) + vs.append(variable_scope.variable(1.0, name="foo_1/bar")) + vs.append(variable_scope.variable(1.0, name="foo_1/bar_1")) + vs.append(variable_scope.variable(1.0, name="foo/bar_1")) + distribute_lib.get_tower_context().merge_call(lambda _: _) + return vs + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + for v in result: + self.assertIsInstance(v, values.MirroredVariable) + self.assertEquals(4, len(result)) + self.assertEquals("foo/bar:0", result[0].name) + self.assertEquals("foo_1/bar:0", result[1].name) + self.assertEquals("foo_1/bar_1:0", result[2].name) + self.assertEquals("foo/bar_1:0", result[3].name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testVariableWithSameCanonicalNameAcrossThreads(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(device_id): + v = variable_scope.variable(1.0, name="foo_" + str(device_id)) + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower( + model_fn, dist.worker_device_index, run_concurrently=False) + self.assertIsInstance(result, values.MirroredVariable) + # The resulting mirrored variable will use the name from the first device. + self.assertEquals("foo_0:0", result.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testWithLayers(self): + self._skip_eager_if_gpus_less_than(1) + def model_fn(features): + with variable_scope.variable_scope("common"): + layer1 = core.Dense(1) + layer1(features) + layer2 = core.Dense(1) + layer2(features) + # This will pause the current thread, and execute the other thread. + distribute_lib.get_tower_context().merge_call(lambda _: _) + layer3 = core.Dense(1) + layer3(features) + return [(layer1.kernel, layer1.bias), + (layer2.kernel, layer2.bias), + (layer3.kernel, layer3.bias)] + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + features = dataset_ops.Dataset.from_tensors([[1.]]).repeat(10) + features = dist.distribute_dataset(features).get_next() + + with dist.scope(): + result = dist.call_for_each_tower( + model_fn, features, run_concurrently=False) + suffixes = ["", "_1", "_2"] + for (kernel, bias), suffix in zip(result, suffixes): + self.assertIsInstance(kernel, values.MirroredVariable) + self.assertEquals("common/dense" + suffix + "/kernel:0", kernel.name) + self.assertIsInstance(bias, values.MirroredVariable) + self.assertEquals("common/dense" + suffix + "/bias:0", bias.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testWithGetVariableAndVariableScope(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(): + v0 = variable_scope.get_variable("var-thread0", [1]) + with variable_scope.variable_scope("common"): + v1 = variable_scope.get_variable("var-thread1", [1]) + # This will pause the current thread, and execute the other thread. + distribute_lib.get_tower_context().merge_call(lambda _: _) + v2 = variable_scope.get_variable("var-thread2", [1]) + + return v0, v1, v2 + + devices = ["/device:CPU:0", "/device:GPU:0"] + dist = mirrored_strategy.MirroredStrategy(devices) + with dist.scope(): + with variable_scope.variable_scope("main"): + v = variable_scope.get_variable("var-main0", [1]) + self.assertEquals("main/var-main0:0", v.name) + + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertEquals(3, len(result)) + v0, v1, v2 = result + self.assertIsInstance(v0, values.MirroredVariable) + self.assertEquals("main/var-thread0:0", v0.name) + self.assertIsInstance(v1, values.MirroredVariable) + self.assertEquals("main/common/var-thread1:0", v1.name) + self.assertIsInstance(v2, values.MirroredVariable) + self.assertEquals("main/common/var-thread2:0", v2.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testThreeDevices(self): + self._skip_eager_if_gpus_less_than(2) + + def model_fn(): + v = variable_scope.variable(1.0, name="foo") + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:GPU:1", "/device:CPU:0"]) + + with dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertIsInstance(result, values.MirroredVariable) + self.assertEquals("foo:0", result.name) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testNonMatchingVariableCreation(self): + self._skip_eager_if_gpus_less_than(1) + + def model_fn(name): + v = variable_scope.variable(1.0, name=name) + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + names = values.DistributedValues({ + "/device:CPU:0": "foo", + "/device:GPU:0": "bar" + }) + with self.assertRaises(RuntimeError): + _ = dist.call_for_each_tower(model_fn, names, run_concurrently=False) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testTowerLocalVariable(self): + self._skip_eager_if_gpus_less_than(1) + + all_v_sum = {} + all_v_mean = {} + + def model_fn(device_id): + tower_context = distribute_lib.get_tower_context() + with tower_context.tower_local_var_scope("sum"): + v_sum = variable_scope.variable(1.0) + with tower_context.tower_local_var_scope("mean"): + v_mean = variable_scope.variable(4.0) + self.assertTrue(isinstance(v_sum, values.TowerLocalVariable)) + self.assertTrue(isinstance(v_mean, values.TowerLocalVariable)) + updates = [v_sum.assign_add(2.0 + device_id), + v_mean.assign(6.0 * device_id)] + all_v_sum[device_id] = v_sum + all_v_mean[device_id] = v_mean + return updates, v_sum, v_mean + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with dist.scope(): + # Create "sum" and "mean" versions of TowerLocalVariables. + ret_ops, ret_v_sum, ret_v_mean = dist.call_for_each_tower( + model_fn, dist.worker_device_index, run_concurrently=False) + # Should see the same wrapping instance in all towers. + self.assertIs(all_v_sum[0], ret_v_sum) + self.assertIs(all_v_mean[0], ret_v_mean) + for i in range(1, dist.num_towers): + self.assertIs(all_v_sum[0], all_v_sum[1]) + self.assertIs(all_v_mean[0], all_v_mean[1]) + + # Apply updates + self.evaluate(variables.global_variables_initializer()) + self.evaluate([y for x in ret_ops for y in dist.unwrap(x)]) + expected_sum = 0.0 + expected_mean = 0.0 + for i, d in enumerate(dist.worker_devices): + # Test access within a device scope, should see different values. + with ops.device(d): + v_sum_value = self.evaluate(ret_v_sum.read_value()) + v_mean_value = self.evaluate(ret_v_mean.read_value()) + expected = i + 3.0 + self.assertEqual(expected, v_sum_value) + expected_sum += expected + expected = i * 6.0 + self.assertEqual(expected, v_mean_value) + expected_mean += expected + + # fetch() should return the value you get by applying the + # reduction across all towers. + self.assertEqual(expected_sum, self.evaluate(dist.fetch(ret_v_sum))) + expected_mean /= len(dist.worker_devices) + self.assertEqual(expected_mean, self.evaluate(dist.fetch(ret_v_mean))) + + # NOTE(priyag): Names and name scopes are ignored in eager, hence we are not + # testing this in eager mode. + + def testNameScope(self): + def model_fn(): + with ops.name_scope("foo"): + a = constant_op.constant(1.0, name="a") + distribute_lib.get_tower_context().merge_call(lambda _: _) + b = constant_op.constant(1.0, name="b") + return a, b + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with context.graph_mode(), dist.scope(): + with ops.name_scope("main"): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertEquals(2, len(result)) + for v, name in zip(result, ["a", "b"]): + self.assertIsInstance(v, values.DistributedValues) + v0, v1 = dist.unwrap(v) + self.assertEquals("main/foo/" + name + ":0", v0.name) + self.assertEquals("main/tower_1/foo/" + name + ":0", v1.name) + + def testWithDefaultName(self): + def model_fn(): + with ops.name_scope(None, "foo"): + a = constant_op.constant(1.0, name="a") + distribute_lib.get_tower_context().merge_call(lambda _: _) + b = constant_op.constant(2.0, name="b") + return a, b + + dist = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:CPU:0"]) + + with context.graph_mode(), dist.scope(): + result = dist.call_for_each_tower(model_fn, run_concurrently=False) + self.assertEquals(2, len(result)) + for v, name in zip(result, ["a", "b"]): + self.assertIsInstance(v, values.DistributedValues) + v0, v1 = dist.unwrap(v) + self.assertEquals("foo/" + name + ":0", v0.name) + self.assertEquals("tower_1/foo/" + name + ":0", v1.name) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_test.py new file mode 100644 index 0000000000..a1ef0ecc77 --- /dev/null +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_test.py @@ -0,0 +1,91 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for class MirroredStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.contrib.distribute.python import strategy_test_lib +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import test_util +from tensorflow.python.ops import variable_scope +from tensorflow.python.training import distribute as distribute_lib + + +@test_util.with_c_api +class MirroredOneCPUDistributionTest(strategy_test_lib.DistributionTestBase): + + def _get_distribution_strategy(self): + return mirrored_strategy.MirroredStrategy(["/device:CPU:0"]) + + def testMinimizeLossEager(self): + self._test_minimize_loss_eager(self._get_distribution_strategy()) + + def testMinimizeLossGraph(self): + self._test_minimize_loss_graph(self._get_distribution_strategy()) + + def testMapReduce(self): + self._test_map_reduce(self._get_distribution_strategy()) + + def testDeviceIndex(self): + self._test_device_index(self._get_distribution_strategy()) + + def testTowerId(self): + self._test_tower_id(self._get_distribution_strategy()) + + @test_util.run_in_graph_and_eager_modes() + def testCallAndMergeExceptions(self): + self._test_call_and_merge_exceptions(self._get_distribution_strategy()) + + +@test_util.with_c_api +class VariableCreatorStackTest(test.TestCase): + + def testCreatorStacksAreThreadLocal(self): + devices = ["/device:CPU:0", "/device:GPU:0"] + dist = mirrored_strategy.MirroredStrategy(devices) + + def model_fn(device_id): + assert isinstance(device_id, int) + def thread_creator_fn(next_creator, *args, **kwargs): + return next_creator(*args, **kwargs) + ":thread_" + str(device_id) + + with variable_scope.variable_creator_scope(thread_creator_fn): + # Create a variable in this scope. + v = variable_scope.variable(1.0) + + # This will pause the current thread, and execute the other thread. + distribute_lib.get_tower_context().merge_call(lambda _: _) + return v + + def main_thread_creator(next_creator, *args, **kwargs): + # We are not using the underlying next_creator for test purposes. + del next_creator, args, kwargs + return "main_thread" + + with context.graph_mode(), \ + dist.scope(), \ + variable_scope.variable_creator_scope(main_thread_creator): + result = dist.call_for_each_tower(model_fn, dist.worker_device_index) + result = dist.unwrap(result) + expected = ["main_thread:thread_0", "main_thread:thread_1"] + self.assertEquals(expected, result) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/monitor.py b/tensorflow/contrib/distribute/python/monitor.py new file mode 100644 index 0000000000..fe80bb4df5 --- /dev/null +++ b/tensorflow/contrib/distribute/python/monitor.py @@ -0,0 +1,61 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Monitor is responsible for training, checkpointing and recovery.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import context +from tensorflow.python.ops import variables + + +class Monitor(object): + """Executes training steps, recovers and checkpoints. + + Note that this class is particularly preliminary, experimental, and + expected to change. + """ + # TODO(isaprykin): Support step functions that need multiple session calls. + # TODO(isaprykin): Support extra arguments to the step function. + # TODO(isaprykin): Support recovery, checkpointing and summaries. + + def __init__(self, step_callable, session=None): + """Initialize the Monitor with components for executing training steps. + + Args: + step_callable: a training `Step` that's capable of signaling when done. + session: a `Session` instance that's needed for graph mode. + + Raises: + ValueError: if `session` was provided for eager mode or not provided for + graph mode. + """ + if context.executing_eagerly(): + if session is not None: + raise ValueError("Should not provide a `session` in Eager mode.") + self._run_step = step_callable + else: + if session is None: + raise ValueError("Should provide a `session` in Graph mode.") + self._run_step = session.make_callable(step_callable()) + session.run(variables.global_variables_initializer()) + + def run_steps(self, num_steps=None): + step = 0 + done = False + while done is not None and (num_steps is None or step < num_steps): + done = self._run_step() + step += 1 diff --git a/tensorflow/contrib/distribute/python/monitor_test.py b/tensorflow/contrib/distribute/python/monitor_test.py new file mode 100644 index 0000000000..8277e1e791 --- /dev/null +++ b/tensorflow/contrib/distribute/python/monitor_test.py @@ -0,0 +1,84 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for class Monitor.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python import monitor as monitor_lib +from tensorflow.contrib.distribute.python import one_device_strategy +from tensorflow.contrib.distribute.python.single_loss_example import single_loss_example +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import ops +from tensorflow.python.training import gradient_descent + + +class MonitorTest(test.TestCase, parameterized.TestCase): + + @combinations.generate( + combinations.times( + combinations.distributions_and_v1_optimizers(), + combinations.combine(mode=combinations.graph_and_eager_modes))) + def testTrainNetwork(self, distribution, optimizer_fn): + with distribution.scope(): + single_loss_step, layer = single_loss_example(optimizer_fn, distribution) + + if context.executing_eagerly(): + monitor = monitor_lib.Monitor(single_loss_step, None) + else: + with self.test_session() as sess: + monitor = monitor_lib.Monitor(single_loss_step, sess) + + monitor.run_steps(1) + + self.assertEqual(1, len(layer.trainable_variables)) + mirrored_weight_variable = layer.trainable_variables[0] + start_error = self.evaluate(distribution.fetch(mirrored_weight_variable)) + start_error = abs(numpy.array(start_error) - 1) + + monitor.run_steps(9) + end_error = self.evaluate(distribution.fetch(mirrored_weight_variable)) + end_error = abs(numpy.array(end_error) - 1) + self.assertGreaterEqual(start_error, end_error) + + def testPassingASessionInEager(self): + distribution = one_device_strategy.OneDeviceStrategy( + "/device:CPU:0") + step_function, _ = single_loss_example( + lambda: gradient_descent.GradientDescentOptimizer(0.2), distribution) + + with self.test_session() as sess: + with self.assertRaisesRegexp(ValueError, "Should not provide"): + _ = monitor_lib.Monitor(step_function, sess) + + def testNotPassingASessionInGraph(self): + distribution = one_device_strategy.OneDeviceStrategy( + "/device:CPU:0") + step_function, _ = single_loss_example( + lambda: gradient_descent.GradientDescentOptimizer(0.2), distribution) + + with context.graph_mode(), ops.Graph().as_default(): + with self.assertRaisesRegexp(ValueError, "Should provide"): + _ = monitor_lib.Monitor(step_function, session=None) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/one_device_strategy.py b/tensorflow/contrib/distribute/python/one_device_strategy.py new file mode 100644 index 0000000000..39c49442b9 --- /dev/null +++ b/tensorflow/contrib/distribute/python/one_device_strategy.py @@ -0,0 +1,148 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Class OneDeviceStrategy implementing DistributionStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import six + +from tensorflow.contrib.distribute.python import values +from tensorflow.contrib.eager.python import datasets +from tensorflow.python.eager import context +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.training import distribute as distribute_lib + + +# TODO(josh11b): Replace asserts in this file with if ...: raise ... + + +class OneDeviceStrategy(distribute_lib.DistributionStrategy): + """A distribution strategy for running on a single device.""" + # TODO(josh11b): Do we wrap values in types to generate errors if you are + # doing something that won't work with other DistributionStrategy + # implementations? + + def __init__(self, device): + super(OneDeviceStrategy, self).__init__() + self._device = device + + def _create_variable(self, next_creator, *args, **kwargs): + # No need to distinguish tower-local variables when not mirroring, + # we just enforce that they are not trainable. + if kwargs.pop("tower_local_reduce_method", None) is not None: + kwargs["trainable"] = False + + colocate_with = kwargs.pop("colocate_with", None) + if colocate_with is None: + with ops.device(self._device): + return next_creator(*args, **kwargs) + if isinstance(colocate_with, six.string_types): + with ops.device(colocate_with): + return next_creator(*args, **kwargs) + if (isinstance(colocate_with, list) and len(colocate_with) == 1 and + isinstance(colocate_with[0], six.string_types)): + with ops.device(colocate_with[0]): + return next_creator(*args, **kwargs) + with ops.colocate_with(colocate_with): + return next_creator(*args, **kwargs) + + def distribute_dataset(self, dataset): + if context.executing_eagerly(): + return datasets.Iterator(dataset) + else: + return dataset.make_one_shot_iterator() + + def _broadcast(self, tensor, destinations): + return tensor + + def _call_for_each_tower(self, fn, *args, **kwargs): + # We don't run `fn` in multiple threads in OneDeviceStrategy. + kwargs.pop("run_concurrently", None) + with ops.device(self._device), _OneDeviceTowerContext(self): + return fn(*args, **kwargs) + + def map(self, map_over, fn, *args, **kwargs): + with ops.device(self._device): + return values.MapOutput([fn(m, *args, **kwargs) for m in map_over]) + + def _reduce(self, method_string, value, destinations): + if not isinstance(value, values.MapOutput): + return value + l = value.get() + assert l + with ops.device(self._device): + if method_string == "sum": + return math_ops.add_n(l) + elif method_string == "mean": + return math_ops.add_n(l) / len(l) + else: + assert False + + def _update(self, var, fn, *args, **kwargs): + with ops.device(self._device), distribute_lib.UpdateContext(self._device): + return fn(var, *args, **kwargs) + + def _update_non_slot(self, colocate_with, fn, *args, **kwargs): + del colocate_with + with ops.device(self._device), distribute_lib.UpdateContext(self._device): + return fn(*args, **kwargs) + + def _fetch(self, val, destination, fn): + """Return a copy of `val` or `fn(val)` on `destination`.""" + with ops.device(self._device): + v = fn(val) + with ops.device(destination): + return array_ops.identity(v) + + def _unwrap(self, value): + return [value] + + @property + def is_single_tower(self): + return True + + @property + def num_towers(self): + return 1 + + @property + def worker_devices(self): + return [self._device] + + @property + def parameter_devices(self): + return [self._device] + + def non_slot_devices(self, var_list): + del var_list + return [self._device] + + def _worker_device_index(self): + return 0 + + +class _OneDeviceTowerContext(distribute_lib.TowerContext): + + def __init__(self, distribution_strategy): + distribute_lib.TowerContext.__init__( + self, distribution_strategy, tower_id=0) + + @property + def device(self): + return self._distribution_strategy.worker_devices[0] diff --git a/tensorflow/contrib/distribute/python/one_device_strategy_test.py b/tensorflow/contrib/distribute/python/one_device_strategy_test.py new file mode 100644 index 0000000000..7101ed0756 --- /dev/null +++ b/tensorflow/contrib/distribute/python/one_device_strategy_test.py @@ -0,0 +1,54 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for class OneDeviceStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import one_device_strategy +from tensorflow.contrib.distribute.python import strategy_test_lib +from tensorflow.python.eager import test +from tensorflow.python.framework import test_util + + +@test_util.with_c_api +class OneDeviceStrategyTest(strategy_test_lib.DistributionTestBase): + + def _get_distribution_strategy(self): + return one_device_strategy.OneDeviceStrategy("/device:CPU:0") + + def testMinimizeLossEager(self): + self._test_minimize_loss_eager(self._get_distribution_strategy()) + + def testMinimizeLossGraph(self): + self._test_minimize_loss_graph(self._get_distribution_strategy()) + + def testMapReduce(self): + self._test_map_reduce(self._get_distribution_strategy()) + + def testDeviceIndex(self): + self._test_device_index(self._get_distribution_strategy()) + + def testTowerId(self): + self._test_tower_id(self._get_distribution_strategy()) + + @test_util.run_in_graph_and_eager_modes() + def testCallAndMergeExceptions(self): + self._test_call_and_merge_exceptions(self._get_distribution_strategy()) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/optimizer_v2_test.py b/tensorflow/contrib/distribute/python/optimizer_v2_test.py new file mode 100644 index 0000000000..a0912b625f --- /dev/null +++ b/tensorflow/contrib/distribute/python/optimizer_v2_test.py @@ -0,0 +1,70 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for running legacy optimizer code with DistributionStrategy.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python.single_loss_example import minimize_loss_example +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import variables + + +class MinimizeLossOptimizerV2Test(test.TestCase, parameterized.TestCase): + + @combinations.generate( + combinations.times( + combinations.distributions_and_v2_optimizers(), + combinations.combine(mode=["graph"], use_callable_loss=[True, False]) + + combinations.combine(mode=["eager"], use_callable_loss=[True]))) + def testTrainNetwork(self, distribution, optimizer_fn, + use_callable_loss=True): + with distribution.scope(): + model_fn, dataset, layer = minimize_loss_example( + optimizer_fn, use_bias=True, use_callable_loss=use_callable_loss) + + iterator = distribution.distribute_dataset(dataset) + + def run_step(): + return control_flow_ops.group(distribution.unwrap( + distribution.call_for_each_tower( + model_fn, iterator.get_next(), run_concurrently=layer.built))) + + if not context.executing_eagerly(): + with self.test_session() as sess: + run_step = sess.make_callable(run_step()) + self.evaluate(variables.global_variables_initializer()) + + weights, biases = [], [] + for _ in range(10): + run_step() + + weights.append(self.evaluate(distribution.fetch(layer.kernel))) + biases.append(self.evaluate(distribution.fetch(layer.bias))) + + error = abs(numpy.add(numpy.squeeze(weights), numpy.squeeze(biases)) - 1) + is_not_increasing = all(y <= x for x, y in zip(error, error[1:])) + self.assertTrue(is_not_increasing) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/prefetching_ops_v2.py b/tensorflow/contrib/distribute/python/prefetching_ops_v2.py new file mode 100644 index 0000000000..b9ffd2f266 --- /dev/null +++ b/tensorflow/contrib/distribute/python/prefetching_ops_v2.py @@ -0,0 +1,167 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Extension of prefetching_ops to support more than one device.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import warnings + +from tensorflow.contrib.data.python.ops import contrib_op_loader # pylint: disable=unused-import +from tensorflow.contrib.data.python.ops import gen_dataset_ops +from tensorflow.contrib.data.python.ops import prefetching_ops +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.ops import iterator_ops +from tensorflow.python.data.util import nest as data_nest +from tensorflow.python.data.util import sparse +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import function +from tensorflow.python.framework import ops +from tensorflow.python.util import nest + + +# pylint: disable=protected-access +class _PrefetchToDeviceIterator(object): + """A replacement for @{tf.data.Iterator} that prefetches to another device.""" + + def __init__(self, input_dataset, devices, buffer_size): + self._input_dataset = input_dataset + self._get_next_call_count = 0 + self._devices = devices + input_iterator = input_dataset.make_one_shot_iterator() + input_iterator_handle = input_iterator.string_handle() + + @function.Defun(dtypes.string) + def _prefetch_fn(handle): + remote_iterator = iterator_ops.Iterator.from_string_handle( + handle, input_iterator.output_types, input_iterator.output_shapes, + input_iterator.output_classes) + return remote_iterator.get_next() + + target_device = gen_dataset_ops.iterator_get_device( + input_iterator._iterator_resource) + self._buffering_resources = [] + for device in nest.flatten(self._devices): + with ops.device(device): + buffer_resource_handle = prefetching_ops.function_buffering_resource( + f=_prefetch_fn, + target_device=target_device, + string_arg=input_iterator_handle, + buffer_size=buffer_size, + thread_pool_size=0) + self._buffering_resources.append(buffer_resource_handle) + + def get_next(self, name=None): + """See @{tf.data.Iterator.get_next}.""" + self._get_next_call_count += 1 + if self._get_next_call_count > iterator_ops.GET_NEXT_CALL_WARNING_THRESHOLD: + warnings.warn(iterator_ops.GET_NEXT_CALL_WARNING_MESSAGE) + + flat_result = [] + # TODO(priyag): This will fail if the input size (typically number of + # batches) is not divisible by number of devices. + # How do we handle that more gracefully / let the user know? + for buffer_resource in self._buffering_resources: + flat_ret = gen_dataset_ops.function_buffering_resource_get_next( + buffer_resource, + output_types=data_nest.flatten(sparse.as_dense_types( + self.output_types, self.output_classes)), name=name) + + ret = sparse.deserialize_sparse_tensors( + data_nest.pack_sequence_as(self.output_types, flat_ret), + self.output_types, self.output_shapes, self.output_classes) + + for tensor, shape in zip( + data_nest.flatten(ret), data_nest.flatten(self.output_shapes)): + if isinstance(tensor, ops.Tensor): + tensor.set_shape(shape) + flat_result.append(ret) + + return nest.pack_sequence_as(self._devices, flat_result) + + @property + def output_classes(self): + return self._input_dataset.output_classes + + @property + def output_shapes(self): + return self._input_dataset.output_shapes + + @property + def output_types(self): + return self._input_dataset.output_types +# pylint: enable=protected-access + + +class _PrefetchToDeviceDataset(dataset_ops.Dataset): + """A `Dataset` whose iterator prefetches elements to other device(s).""" + + def __init__(self, input_dataset, devices, buffer_size): + self._input_dataset = input_dataset + self._devices = devices + self._buffer_size = buffer_size if buffer_size is not None else 1 + + def make_one_shot_iterator(self): + return _PrefetchToDeviceIterator(self._input_dataset, self._devices, + self._buffer_size) + + def make_initializable_iterator(self, shared_name=None): + raise NotImplementedError("`prefetch_to_devices()` is not currently " + "compatible with initializable iterators. Use " + "`make_one_shot_iterator()` instead.") + + def _as_variant_tensor(self): + # TODO(mrry): Raise this error earlier (e.g. when one of the Dataset + # transformation methods is called. + # TODO(mrry): Investigate support for chaining further transformations after + # the prefetch, including GPU support. + raise NotImplementedError("`prefetch_to_devices()` must be the last " + "transformation in a dataset pipeline.") + + # TODO(priyag): Fix the output types, shapes and classes to match the result + # of get_next (which has the additional nesting layer of devices now). + @property + def output_types(self): + return self._input_dataset.output_types + + @property + def output_shapes(self): + return self._input_dataset.output_shapes + + @property + def output_classes(self): + return self._input_dataset.output_classes + + +def prefetch_to_devices(devices, buffer_size=None): + """A transformation that prefetches dataset values to the given `devices`. + + NOTE: Although the transformation creates a @{tf.data.Dataset}, the + transformation must be the final `Dataset` in the input pipeline. + + Args: + devices: A nested structure of devices on which to prefetch the data. It can + be a single device name, or a tuple or list of device names. + buffer_size: (Optional.) The number of elements to buffer on each device. + Defaults to an automatically chosen value. + + Returns: + A `Dataset` transformation function, which can be passed to + @{tf.data.Dataset.apply}. + """ + def _apply_fn(dataset): + return _PrefetchToDeviceDataset(dataset, devices, buffer_size) + + return _apply_fn diff --git a/tensorflow/contrib/distribute/python/prefetching_ops_v2_test.py b/tensorflow/contrib/distribute/python/prefetching_ops_v2_test.py new file mode 100644 index 0000000000..8ed16f4607 --- /dev/null +++ b/tensorflow/contrib/distribute/python/prefetching_ops_v2_test.py @@ -0,0 +1,68 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for prefetching_ops_v2.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import prefetching_ops_v2 +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.framework import errors +from tensorflow.python.framework import test_util +from tensorflow.python.platform import test + + +class PrefetchingOpsV2Test(test.TestCase): + + def testPrefetchToOneDevice(self): + if not test_util.is_gpu_available(): + self.skipTest("No GPU available") + + host_dataset = dataset_ops.Dataset.range(10) + device_dataset = host_dataset.apply( + prefetching_ops_v2.prefetch_to_devices("/gpu:0")) + + iterator = device_dataset.make_one_shot_iterator() + next_element = iterator.get_next() + + with self.test_session() as sess: + for i in range(10): + self.assertEqual(i, sess.run(next_element)) + with self.assertRaises(errors.OutOfRangeError): + sess.run(next_element) + + def testPrefetchToTwoDevicesInAList(self): + if not test_util.is_gpu_available(): + self.skipTest("No GPU available") + + host_dataset = dataset_ops.Dataset.range(10) + device_dataset = host_dataset.apply( + prefetching_ops_v2.prefetch_to_devices(["/cpu:0", "/gpu:0"])) + + iterator = device_dataset.make_one_shot_iterator() + next_element = iterator.get_next() + + output = [] + with self.test_session() as sess: + for _ in range(5): + result = sess.run(next_element) + self.assertEqual(2, len(result)) + output.extend(result) + self.assertEquals(set(range(10)), set(output)) + with self.assertRaises(errors.OutOfRangeError): + sess.run(next_element) + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/shared_variable_creator.py b/tensorflow/contrib/distribute/python/shared_variable_creator.py new file mode 100644 index 0000000000..aca9c7af05 --- /dev/null +++ b/tensorflow/contrib/distribute/python/shared_variable_creator.py @@ -0,0 +1,97 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Utility to re-use variables created on first device on subsequent devices.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import re + +_VARIABLE_UNIQUIFYING_REGEX = re.compile(r"_\d/") +_VARIABLE_UNIQUIFYING_REGEX_AT_END = re.compile(r"_\d$") + + +def _canonicalize_variable_name(name): + # If no name is specified, uses default name "Variable". + if name is None: + return "Variable" + # Replace all instances of "_/" with "/" + name = _VARIABLE_UNIQUIFYING_REGEX.sub("/", name) + # Replace any instances of "_" at the end of the string with "" + name = _VARIABLE_UNIQUIFYING_REGEX_AT_END.sub("", name) + return name + + +def make_fn(shared_variable_store, device_id): + """Construct the variable creator function for device `device_id`. + + Constructs custom variable creator functions for the given device. + On first device (device_id == 0), it creates the variable using the + `next_creator`, and stores it in the provided `shared_variable_store`. + On all other devices (device_id > 0), it tries to re-use the variable + already created with the same name. If no such variable exists, it throws an + error. + Additionally, we de-uniquify variable names before checking for matches. This + helps re-use variables which are intended to be the same but have different + names due to variable uniquificaton happening upstream. Since this might + mean we may have multiple variables with the same canonical name, we store + them in a list per canonical name and return them in the same order as well. + + Args: + shared_variable_store: A dictionary that we will use to store variables + created on the first device, and re-used by creators for other devices. + device_id: Integer index of the device whose creator should be + constructed. + + Returns: + An appropriate creator function based on device_id. + + """ + variable_scope_access_index = {} + assert isinstance(device_id, int) + + def create_new_variable(next_creator, *args, **kwargs): + """Create the variable using `next_creator` and store it.""" + canonical_name = _canonicalize_variable_name(kwargs.get("name")) + v = next_creator(*args, **kwargs) + + if canonical_name not in shared_variable_store: + shared_variable_store[canonical_name] = [] + shared_variable_store[canonical_name].append(v) + return v + + def reuse_variable(next_creator, *args, **kwargs): + """Re-use existing variable from store with same name (in order).""" + del next_creator, args + name = kwargs.get("name") + canonical_name = _canonicalize_variable_name(name) + + try: + variable_index = variable_scope_access_index.get(canonical_name, 0) + v = shared_variable_store[canonical_name][variable_index] + # TODO(priyag): Make this variable re-use more robust by adding checks + # that the requested shape and dtype match the existing variable. + variable_scope_access_index[canonical_name] = variable_index + 1 + return v + except (KeyError, IndexError): + raise RuntimeError( + "Tried to create variable {} with mismatching name on device {}". + format(name, device_id)) + + if device_id == 0: + return create_new_variable + else: + return reuse_variable diff --git a/tensorflow/contrib/distribute/python/shared_variable_creator_test.py b/tensorflow/contrib/distribute/python/shared_variable_creator_test.py new file mode 100644 index 0000000000..713494d603 --- /dev/null +++ b/tensorflow/contrib/distribute/python/shared_variable_creator_test.py @@ -0,0 +1,75 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for SharedVariableCreator.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import shared_variable_creator +from tensorflow.python.eager import test +from tensorflow.python.framework import test_util +from tensorflow.python.ops import variable_scope + + +class CanonicalizeVariableNameTest(test.TestCase): + + def _canonicalize(self, name): + return shared_variable_creator._canonicalize_variable_name(name) + + def testNoName(self): + self.assertEquals("Variable", self._canonicalize(None)) + + def testPatternInMiddle(self): + self.assertEquals("foo/bar/baz", self._canonicalize("foo_1/bar_1/baz")) + + def testPatternAtEnd(self): + self.assertEquals("foo", self._canonicalize("foo_1")) + + def testWrongPatterns(self): + self.assertEquals("foo_1:0", self._canonicalize("foo_1:0")) + self.assertEquals("foo1", self._canonicalize("foo1")) + self.assertEquals("foo_a", self._canonicalize("foo_a")) + + +@test_util.with_c_api +class SharedVariableCreatorTest(test.TestCase): + + @test_util.run_in_graph_and_eager_modes() + def testSharedVariable(self): + + shared_variable_store = {} + num_devices = 3 + creator_fns = [] + for i in range(num_devices): + creator_fn = shared_variable_creator.make_fn(shared_variable_store, i) + creator_fns.append(creator_fn) + + with variable_scope.variable_creator_scope(creator_fns[0]): + v0 = variable_scope.variable(1.0, name="foo") + + with variable_scope.variable_creator_scope(creator_fns[1]): + v1 = variable_scope.variable(1.0, name="foo") + + with variable_scope.variable_creator_scope(creator_fns[2]): + v2 = variable_scope.variable(1.0, name="foo") + + # v1 and v2 should be same as v0 + self.assertIs(v1, v0) + self.assertIs(v2, v0) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/simple_estimator_example.py b/tensorflow/contrib/distribute/python/simple_estimator_example.py new file mode 100644 index 0000000000..7095d801ad --- /dev/null +++ b/tensorflow/contrib/distribute/python/simple_estimator_example.py @@ -0,0 +1,97 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""A simple example to test the a DistributionStrategy with Estimators. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.estimator import estimator as estimator_lib +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.estimator import run_config +from tensorflow.python.framework import constant_op +from tensorflow.python.layers import core +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import app +from tensorflow.python.training import gradient_descent +from tensorflow.python.training import training_util + + +def build_model_fn_optimizer(): + """Simple model_fn with optimizer.""" + # TODO(anjalisridhar): Move this inside the model_fn once OptimizerV2 is + # done? + optimizer = gradient_descent.GradientDescentOptimizer(0.2) + + def model_fn(features, labels, mode): # pylint: disable=unused-argument + """model_fn which uses a single unit Dense layer.""" + # You can also use the Flatten layer if you want to test a model without any + # weights. + layer = core.Dense(1, use_bias=True) + logits = layer(features) + + if mode == model_fn_lib.ModeKeys.PREDICT: + predictions = {"logits": logits} + return model_fn_lib.EstimatorSpec(mode, predictions=predictions) + + def loss_fn(): + y = array_ops.reshape(logits, []) - constant_op.constant(1.) + return y * y + + if mode == model_fn_lib.ModeKeys.EVAL: + return model_fn_lib.EstimatorSpec(mode, loss=loss_fn()) + + assert mode == model_fn_lib.ModeKeys.TRAIN + + global_step = training_util.get_global_step() + train_op = optimizer.minimize(loss_fn(), global_step=global_step) + return model_fn_lib.EstimatorSpec(mode, loss=loss_fn(), train_op=train_op) + + return model_fn + + +def main(_): + distribution = mirrored_strategy.MirroredStrategy( + ["/device:GPU:0", "/device:GPU:1"]) + config = run_config.RunConfig(distribute=distribution) + + def input_fn(): + features = dataset_ops.Dataset.from_tensors([[1.]]).repeat(10) + labels = dataset_ops.Dataset.from_tensors([1.]).repeat(10) + return dataset_ops.Dataset.zip((features, labels)) + + estimator = estimator_lib.Estimator( + model_fn=build_model_fn_optimizer(), config=config) + estimator.train(input_fn=input_fn, steps=10) + + eval_result = estimator.evaluate(input_fn=input_fn) + print("Eval result: {}".format(eval_result)) + + def predict_input_fn(): + predict_features = dataset_ops.Dataset.from_tensors([[1.]]).repeat(10) + return predict_features + + predictions = estimator.predict(input_fn=predict_input_fn) + # TODO(anjalsridhar): This returns a generator object, figure out how to get + # meaningful results here. + print("Prediction results: {}".format(predictions)) + + +if __name__ == "__main__": + app.run(main) diff --git a/tensorflow/contrib/distribute/python/single_loss_example.py b/tensorflow/contrib/distribute/python/single_loss_example.py new file mode 100644 index 0000000000..cef5fd2f89 --- /dev/null +++ b/tensorflow/contrib/distribute/python/single_loss_example.py @@ -0,0 +1,102 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""A simple network to use in tests and examples.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.distribute.python import step_fn +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.framework import constant_op +from tensorflow.python.layers import core +from tensorflow.python.layers import normalization +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops + + +def single_loss_example(optimizer_fn, distribution, use_bias=False): + """Build a very simple network to use in tests and examples.""" + dataset = dataset_ops.Dataset.from_tensors([[1.]]).repeat() + optimizer = optimizer_fn() + layer = core.Dense(1, use_bias=use_bias) + + def loss_fn(x): + y = array_ops.reshape(layer(x), []) - constant_op.constant(1.) + return y * y + + single_loss_step = step_fn.StandardSingleLossStep(dataset, loss_fn, optimizer, + distribution) + + # Layer is returned for inspecting the kernels in tests. + return single_loss_step, layer + + +def minimize_loss_example(optimizer_fn, + use_bias=False, + use_callable_loss=True, + create_optimizer_inside_model_fn=False): + """Example of non-distribution-aware legacy code.""" + dataset = dataset_ops.Dataset.from_tensors([[1.]]).repeat() + # An Optimizer instance is created either outside or inside model_fn. + outer_optimizer = None + if not create_optimizer_inside_model_fn: + outer_optimizer = optimizer_fn() + + layer = core.Dense(1, use_bias=use_bias) + + def model_fn(x): + """A very simple model written by the user.""" + + def loss_fn(): + y = array_ops.reshape(layer(x), []) - constant_op.constant(1.) + return y * y + + optimizer = outer_optimizer or optimizer_fn() + + if use_callable_loss: + return optimizer.minimize(loss_fn) + else: + return optimizer.minimize(loss_fn()) + + return model_fn, dataset, layer + + +def batchnorm_example(optimizer_fn, + batch_per_epoch=1, + momentum=0.9, + renorm=False): + """Example of non-distribution-aware legacy code with batch normalization.""" + # input shape is [16, 8], input values are increasing in both dimensions. + dataset = dataset_ops.Dataset.from_tensor_slices( + [[[float(x * 8 + y + z * 100) + for y in range(8)] + for x in range(16)] + for z in range(batch_per_epoch)]).repeat() + optimizer = optimizer_fn() + batchnorm = normalization.BatchNormalization( + renorm=renorm, momentum=momentum, fused=False) + + def model_fn(x): + + def loss_fn(): + y = math_ops.reduce_sum(batchnorm(x, training=True), axis=1) + loss = math_ops.reduce_mean(y - constant_op.constant(1.)) + return loss + + # Callable loss. + return optimizer.minimize(loss_fn) + + return model_fn, dataset, batchnorm diff --git a/tensorflow/contrib/distribute/python/step_fn.py b/tensorflow/contrib/distribute/python/step_fn.py new file mode 100644 index 0000000000..82514c64be --- /dev/null +++ b/tensorflow/contrib/distribute/python/step_fn.py @@ -0,0 +1,103 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""The step function abstraction represents a single training step.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.eager import backprop +from tensorflow.python.training import optimizer as optimizer_lib + + +class Step(object): + """Interface for performing each step of a training algorithm.""" + + def __init__(self, distribution): + self._distribution = distribution + + @property + def distribution(self): + return self._distribution + + def __call__(self): + """Perform one step of this training algorithm.""" + return self.step(self.inputs()) + + def inputs(self): + """For the generating the input to be passed to `step()`.""" + raise NotImplementedError("must be implemented in descendants") + + def step(self, inputs): + """Perform the main computation of this training algorithm.""" + raise NotImplementedError("must be implemented in descendants") + + +class StandardInputStep(Step): + """Step with a standard implementation of input handling. + + Args: + input_dataset: a tf.data Dataset that provides input. + """ + + def __init__(self, input_dataset, distribution): + Step.__init__(self, distribution) + self._distributed_input = distribution.distribute_dataset(input_dataset) + + def inputs(self): + return self._distributed_input.get_next() + + +class StandardSingleLossStep(StandardInputStep): + """A step function that implements a training step for a feed forward network. + + An instance of this class is intended to be used as a callable: + + ```python + ... + step = step_fn.StandardSingleLossStep(dataset, loss_fn, optimizer) + step.initialize(distribution) + + # Run a single training step on a given DistributionStrategy: + step(distribution) + ... + ``` + + Args: + input_dataset: a tf.data Dataset that provides input. + loss_fn: a function that returns loss. + optimizer: an optimizer that implements an update rule. + distribution: a `DistributionStrategy` object. + """ + + def __init__(self, input_dataset, loss_fn, optimizer, distribution): + StandardInputStep.__init__(self, input_dataset, distribution) + self._loss_fn = loss_fn + self._optimizer = optimizer + self._is_run_concurrently = False + + def step(self, inputs): + with self._distribution.scope(): + gradients_fn = backprop.implicit_grad(self._loss_fn) + gradients_fn = optimizer_lib.get_filtered_grad_fn(gradients_fn) + + grads_and_vars = self.distribution.call_for_each_tower( + gradients_fn, inputs, run_concurrently=self._is_run_concurrently) + # If threads use layers, then we need to run the first step sequentially, + # so that layers.build() is not executed in parallel. Otherwise, multiple + # sets of mirrored variables are going to be created. + self._is_run_concurrently = True + return self._optimizer._distributed_apply( # pylint: disable=protected-access + self.distribution, grads_and_vars) diff --git a/tensorflow/contrib/distribute/python/step_fn_test.py b/tensorflow/contrib/distribute/python/step_fn_test.py new file mode 100644 index 0000000000..75c5ec9659 --- /dev/null +++ b/tensorflow/contrib/distribute/python/step_fn_test.py @@ -0,0 +1,62 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for class Step.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy + +from tensorflow.contrib.distribute.python import combinations +from tensorflow.contrib.distribute.python.single_loss_example import single_loss_example +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.ops import variables + + +class SingleLossStepTest(test.TestCase, parameterized.TestCase): + + @combinations.generate( + combinations.times( + combinations.distributions_and_v1_optimizers(), + combinations.combine(mode=combinations.graph_and_eager_modes))) + def testTrainNetwork(self, distribution, optimizer_fn): + with distribution.scope(): + single_loss_step, layer = single_loss_example( + optimizer_fn, distribution, use_bias=True) + + if context.executing_eagerly(): + run_step = single_loss_step + else: + with self.test_session() as sess: + run_step = sess.make_callable(single_loss_step()) + self.evaluate(variables.global_variables_initializer()) + + weights, biases = [], [] + for _ in range(10): + run_step() + + weights.append(self.evaluate(distribution.fetch(layer.kernel))) + biases.append(self.evaluate(distribution.fetch(layer.bias))) + + error = abs(numpy.add(numpy.squeeze(weights), numpy.squeeze(biases)) - 1) + is_not_increasing = all(y <= x for x, y in zip(error, error[1:])) + self.assertTrue(is_not_increasing) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/distribute/python/strategy_test_lib.py b/tensorflow/contrib/distribute/python/strategy_test_lib.py new file mode 100644 index 0000000000..2b4ad9f146 --- /dev/null +++ b/tensorflow/contrib/distribute/python/strategy_test_lib.py @@ -0,0 +1,225 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Library for testing DistributionStrategy descendants.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.eager import backprop +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import ops +from tensorflow.python.layers import core +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variables +from tensorflow.python.training import distribute as distribute_lib +from tensorflow.python.training import optimizer + + +class _TestException(Exception): + pass + + +# May be the argument to either distribution.call_for_each_tower() or +# get_tower_context().merge_call() +def _raise_exception_fn(_=None): + raise _TestException() + + +# Must be the argument to a distribution.call_for_each_tower() call, calls a +# get_tower_context().merge_call() that raises an exception. +def _merge_raises_fn(): + distribute_lib.get_tower_context().merge_call(_raise_exception_fn) + + +# Must be the argument to a get_tower_context().merge_call() call, calls +# dist.call_for_each_tower() with a function that raises an exception. +def _call_raises_fn(dist): + dist.call_for_each_tower(_raise_exception_fn) + + +# Must be the argument to a distribution.call_for_each_tower() call, +# calls a get_tower_context().merge_call() that calls a +# call_for_each_tower() that raises an exception. +def _merge_call_raises_fn(): + distribute_lib.get_tower_context().merge_call(_call_raises_fn) + + +# Must be the argument to a get_tower_context().merge_call() call, calls +# dist.call_for_each_tower() with a function that calls a +# get_tower_context().merge_call() that raises an exception. +def _call_merge_raises_fn(dist): + dist.call_for_each_tower(_merge_raises_fn) + + +# Must be the argument to a distribution.call_for_each_tower() call, calls a +# get_tower_context().merge_call() that calls a call_for_each_tower() that +# calls a get_tower_context().merge_call() that raises an exception. +def _merge_call_merge_raises_fn(): + distribute_lib.get_tower_context().merge_call(_call_merge_raises_fn) + + +class DistributionTestBase(test.TestCase): + """Some tests that should work with any DistributionStrategy.""" + + def _test_minimize_loss_eager(self, d): + with d.scope(): + l = core.Dense(1, use_bias=False) + + def loss(x): + # TODO(josh11b): What if this constant was instead a captured + # value? Would it need to be a value that has been passed + # through d.broadcast()? + y = array_ops.reshape(l(x), []) - constant_op.constant(1.) + return y * y + # TODO(isaprykin): Extract implicit_grad+get_filtered_grad_fn into a + # common `implicit_grad` function and put it in DistributionStrategy. + grad_fn = backprop.implicit_grad(loss) + grad_fn = optimizer.get_filtered_grad_fn(grad_fn) + + def update(v, g): + return v.assign_sub(0.2 * g) + + one = d.broadcast(constant_op.constant([[1.]])) + + def step(): + """Perform one optimization step.""" + # Run forward & backward to get gradients, variables list. + g_v = d.call_for_each_tower(grad_fn, one, run_concurrently=l.built) + + # Update the variables using the gradients and the update() function. + before_list = [] + after_list = [] + for g, v in g_v: + fetched = d.fetch(v) + before_list.append(fetched) + # control_dependencies irrelevant but harmless in eager execution + with ops.control_dependencies([fetched]): + g = d.reduce("sum", g, destinations=v) + with ops.control_dependencies(d.unwrap(d.update(v, update, g))): + after_list.append(d.fetch(v)) + return before_list, after_list + + for i in range(10): + b, a = step() + if i == 0: + before, = b # pylint: disable=unbalanced-tuple-unpacking + after, = a # pylint: disable=unbalanced-tuple-unpacking + + error_before = abs(before.numpy() - 1) + error_after = abs(after.numpy() - 1) + # Error should go down + self.assertLess(error_after, error_before) + + def _test_minimize_loss_graph(self, d, soft_placement=False): + config = config_pb2.ConfigProto() + config.allow_soft_placement = soft_placement + config.gpu_options.per_process_gpu_memory_fraction = 0.3 + with context.graph_mode(), \ + ops.Graph().as_default(), \ + self.test_session(config=config) as sess, \ + d.scope(): + l = core.Dense(1, use_bias=False) + + def loss(x): + # TODO(josh11b): What if this constant was instead a captured + # value? Would it need to be a value that has been passed + # through d.broadcast()? + y = array_ops.reshape(l(x), []) - constant_op.constant(1.) + return y * y + + grad_fn = backprop.implicit_grad(loss) + + def update(v, g): + return v.assign_sub(0.2 * g) + + one = d.broadcast(constant_op.constant([[1.]])) + + def step(): + """Perform one optimization step.""" + # Run forward & backward to get gradients, variables list. + g_v = d.call_for_each_tower(grad_fn, one) + + # Update the variables using the gradients and the update() function. + before_list = [] + after_list = [] + for g, v in g_v: + fetched = d.fetch(v) + before_list.append(fetched) + with ops.control_dependencies([fetched]): + g = d.reduce("sum", g, destinations=v) + with ops.control_dependencies(d.unwrap(d.update(v, update, g))): + after_list.append(d.fetch(v)) + return before_list, after_list + + before_out, after_out = step() + variables.global_variables_initializer().run() + for i in range(10): + b, a = sess.run((before_out, after_out)) + if i == 0: + before, = b + after, = a + + error_before = abs(before - 1) + error_after = abs(after - 1) + # Error should go down + self.assertLess(error_after, error_before) + + def _test_map_reduce(self, d, in_graph=None): + with d.scope(): + map_in = [constant_op.constant(i) for i in range(10)] + map_out = d.map(map_in, lambda x, y: x * y, 2) + observed = d.fetch(d.reduce("sum", map_out)) + expected = 90 # 2 * (0 + 1 + ... + 9) + self.assertEqual(expected, observed.numpy()) + + def _test_device_index(self, d): + with d.scope(): + expected_devices = [False] * len(d.worker_devices) + + def mark_devices_fn(device_id): + self.assertLess(device_id, len(d.worker_devices)) + self.assertFalse(expected_devices[device_id]) + expected_devices[device_id] = True + + d.call_for_each_tower(mark_devices_fn, d.worker_device_index) + self.assertAllEqual(expected_devices, [True] * len(d.worker_devices)) + + def _test_tower_id(self, d): + with d.scope(): + expected_devices = [False] * len(d.worker_devices) + + def mark_devices_fn(): + tower_id = distribute_lib.get_tower_context().tower_id + self.assertLess(tower_id, len(d.worker_devices)) + self.assertFalse(expected_devices[tower_id]) + expected_devices[tower_id] = True + + d.call_for_each_tower(mark_devices_fn) + self.assertAllEqual(expected_devices, [True] * len(d.worker_devices)) + + def _test_call_and_merge_exceptions(self, dist): + with dist.scope(): + with self.assertRaises(_TestException): + dist.call_for_each_tower(_raise_exception_fn) + with self.assertRaises(_TestException): + dist.call_for_each_tower(_merge_raises_fn) + with self.assertRaises(_TestException): + dist.call_for_each_tower(_merge_call_raises_fn) + with self.assertRaises(_TestException): + dist.call_for_each_tower(_merge_call_merge_raises_fn) diff --git a/tensorflow/contrib/distribute/python/values.py b/tensorflow/contrib/distribute/python/values.py new file mode 100644 index 0000000000..c1ba22ed5a --- /dev/null +++ b/tensorflow/contrib/distribute/python/values.py @@ -0,0 +1,575 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Various classes representing distributed values. + +See go/tf-distribution-strategy. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import weakref + +import six + +from tensorflow.contrib.data.python.ops import batching +from tensorflow.contrib.distribute.python import prefetching_ops_v2 +from tensorflow.contrib.eager.python import datasets +from tensorflow.python.eager import context +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.training import checkpointable +from tensorflow.python.training import device_util +from tensorflow.python.training import distribute as distribute_lib +from tensorflow.python.training import saver +from tensorflow.python.util import nest + + +# pylint: disable=line-too-long +# TODO(josh11b): Should device values be strings or DeviceSpec objects +# Not sure DeviceSpec objects are usable as a dict key. +class DistributedValues(object): + """Holds a map from device to values. Either PerDevice or Mirrored.""" + + def __init__(self, index): + self._index = {device_util.canonicalize(key): value + for key, value in six.iteritems(index)} + + def get(self, device=None): + """Returns the value for the current device or raises a ValueError.""" + if device is None: + tower_context = distribute_lib.get_tower_context() + if tower_context: + device = tower_context.device + else: + device = distribute_lib.get_update_device() + if device is None: + device = device_util.current() + device = device_util.canonicalize(device) + try: + return self._index[device] + except KeyError: + raise ValueError("Device %s not found in %s (current device %s)" % + (device, self._index.keys(), device_util.current())) + + def on_device(self, device): + device = device_util.canonicalize(device) + return device in self._index + + @property + def devices(self): + return self._index.keys() + + def __str__(self): + return "%s:%s" % (self.__class__.__name__, self._index) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self._index) + + # TODO(josh11b): Possibly make an accessor for _index for use by + # DistributionStrategy implementations. + + +class DistributedDelegate(DistributedValues): + """A map from device to values; acts as the same type as the values.""" + + def __init__(self, index): + super(DistributedDelegate, self).__init__(index) + + def __getattr__(self, name): + return getattr(self.get(), name) + + # pylint: disable=multiple-statements + def __add__(self, o): return self.get() + o + def __radd__(self, o): return o + self.get() + def __sub__(self, o): return self.get() - o + def __rsub__(self, o): return o - self.get() + def __mul__(self, o): return self.get() * o + def __rmul__(self, o): return o * self.get() + def __truediv__(self, o): return self.get() / o + def __rtruediv__(self, o): return o / self.get() + def __floordiv__(self, o): return self.get() // o + def __rfloordiv__(self, o): return o // self.get() + def __mod__(self, o): return self.get() % o + def __rmod__(self, o): return o % self.get() + def __lt__(self, o): return self.get() < o + def __le__(self, o): return self.get() <= o + def __gt__(self, o): return self.get() > o + def __ge__(self, o): return self.get() >= o + def __and__(self, o): return self.get() & o + def __rand__(self, o): return o & self.get() + def __or__(self, o): return self.get() | o + def __ror__(self, o): return o | self.get() + def __xor__(self, o): return self.get() ^ o + def __rxor__(self, o): return o ^ self.get() + def __getitem__(self, o): return self.get()[o] + def __pow__(self, o, modulo=None): return pow(self.get(), o, modulo) + def __rpow__(self, o): return pow(o, self.get()) + def __invert__(self): return ~self.get() + def __neg__(self): return -self.get() + def __abs__(self): return abs(self.get()) + + def __div__(self, o): + try: + return self.get().__div__(o) + except AttributeError: + # See https://docs.python.org/3/library/constants.html#NotImplemented + return NotImplemented + + def __rdiv__(self, o): + try: + return self.get().__rdiv__(o) + except AttributeError: + # See https://docs.python.org/3/library/constants.html#NotImplemented + return NotImplemented + + def __matmul__(self, o): + try: + return self.get().__matmul__(o) + except AttributeError: + # See https://docs.python.org/3/library/constants.html#NotImplemented + return NotImplemented + + def __rmatmul__(self, o): + try: + return self.get().__rmatmul__(o) + except AttributeError: + # See https://docs.python.org/3/library/constants.html#NotImplemented + return NotImplemented + + # TODO(josh11b): Even more operator overloads. + + +class PerDevice(DistributedValues): + """Holds a map from device to unsynchronized values.""" + pass + + +class Mirrored(DistributedValues): + """Holds a map from device to values which are kept in sync.""" + pass + + +def _assign_on_device(device, variable, tensor): + with ops.device(device): + return variable.assign(array_ops.identity(tensor)) + + +DistributedVarOp = collections.namedtuple( + "DistributedVarOp", ["name", "graph", "type"]) + + +class DistributedVariable(DistributedDelegate): + """Holds a map from device to variables.""" + # TODO(josh11b): Support changing the set of variables if e.g. if new + # devices are joining or a device is to leave. + + def __init__(self, index): + # Child class must set self._primary_var before calling + # super(...).__init__(index). + self._common_name = self._primary_var.name.split(":")[0] + super(DistributedVariable, self).__init__(index) + + @property + def initializer(self): + return control_flow_ops.group([v.initializer for v in self._index.values()]) + + @property + def graph(self): + return self._primary_var.graph + + @property + def _shared_name(self): + return self._common_name + + @property + def _unique_id(self): + return self._primary_var._unique_id # pylint: disable=protected-access + + @property + def name(self): + return self._primary_var.name + + @property + def dtype(self): + return self._primary_var.dtype + + @property + def shape(self): + return self._primary_var.shape + + def get_shape(self): + return self._primary_var.get_shape() + + @property + def op(self): + # We want cross-tower code that does some var.op.X calls + # to work (even if the current device isn't in self.devices), but + # other uses of var.op in a cross-tower context to fail. + if distribute_lib.get_cross_tower_context(): + return DistributedVarOp(self._primary_var.op.name, + self._primary_var.op.graph, + self._primary_var.op.type) + return self.get().op + + def _should_act_as_resource_variable(self): + """Pass resource_variable_ops.is_resource_variable check.""" + pass + + +# Register a conversion function which reads the value of the variable, +# allowing instances of the class to be used as tensors. +def _tensor_conversion(var, dtype=None, name=None, as_ref=False): + # Try to avoid assignments to and other mutations of MirroredVariable + # state except through a DistributionStrategy.update() call. + assert not as_ref + return ops.internal_convert_to_tensor( + var.get(), dtype=dtype, name=name, as_ref=as_ref) + + +ops.register_tensor_conversion_function(DistributedVariable, _tensor_conversion) +# TODO(josh11b): ops.register_dense_tensor_like_type(DistributedVariable)? + + +class _MirroredSaveable(saver.BaseSaverBuilder.ResourceVariableSaveable): + """Class for defining how to restore a MirroredVariable.""" + + def __init__(self, mirrored_variable, primary_variable, name): + self._mirrored_variable = mirrored_variable + super(_MirroredSaveable, self).__init__(primary_variable, "", name) + + def restore(self, restored_tensors, restored_shapes): + """Restore the same value into all variables.""" + tensor, = restored_tensors + return control_flow_ops.group([ + _assign_on_device(d, v, tensor) + for d, v in six.iteritems(self._mirrored_variable._index)]) # pylint: disable=protected-access + + +def _get_update_device(): + """Validate we are in update/update_non_slot() and return current device. + + This is used in MirroredVariable.assign* members, to make sure they + are only called via an update method, to make sure all components of the + variable are being updated in a consistent way. + + Returns: + A string device. + + Raises: + RuntimeError: If not in distribution.update()/.update_non_slot(). + """ + device = distribute_lib.get_update_device() + if device is None: + raise RuntimeError( + "Use DistributionStrategy.update() to modify a MirroredVariable.") + return device + + +class MirroredVariable(DistributedVariable, Mirrored, + checkpointable.CheckpointableBase): + """Holds a map from device to variables whose values are kept in sync.""" + + def __init__(self, index, primary_var): + # Use a weakref to make it easy to map from the contained values + # to the container without introducing a reference cycle. + for v in six.itervalues(index): + v._mirrored_container = weakref.ref(self) # pylint: disable=protected-access + self._primary_var = primary_var + super(MirroredVariable, self).__init__(index) + + # We use _get_update_device() for the assign* methods to enforce + # that we are in an update() function. The arguments to update() are + # automatically unwrapped so the update() function would normally + # see regular variables, not MirroredVariables. However, the update + # function can still operate on wrapped MirroredVariables through + # object members, captured arguments, etc. This is more likely in an + # update_non_slot() function (like OptimizerV2._finish), which can + # update several non-slot variables in one call. + def assign_sub(self, *args, **kwargs): + return self.get(device=_get_update_device()).assign_sub(*args, **kwargs) + + def assign_add(self, *args, **kwargs): + return self.get(device=_get_update_device()).assign_add(*args, **kwargs) + + def assign(self, *args, **kwargs): + return self.get(device=_get_update_device()).assign(*args, **kwargs) + + def _gather_saveables_for_checkpoint(self): + """Overrides CheckpointableBase method. + + This allows both name-based and object-based save and restore of + MirroredVariables. + + Returns: + A dictionary mapping attribute names to `SaveableObject` factories. + """ + def _saveable_factory(name=self._common_name): + return _MirroredSaveable(self, self._primary_var, name) + return {checkpointable.VARIABLE_VALUE_KEY: _saveable_factory} + + +class _TowerLocalSaveable(saver.BaseSaverBuilder.SaveableObject): + """Class for defining how to restore a TowerLocalVariable.""" + + def __init__(self, tower_local_variable, name): + self._tower_local_variable = tower_local_variable + # We use a callable so that we don't have to evaluate this expression + # in the case where we are trying to restore instead of save. + def tensor(): + return distribute_lib.get_distribution_strategy().fetch( + tower_local_variable) + spec = saver.BaseSaverBuilder.SaveSpec( + tensor=tensor, + slice_spec="", + name=name, + dtype=tower_local_variable.dtype) + super(_TowerLocalSaveable, self).__init__(tensor, [spec], name) + + def restore(self, restored_tensors, restored_shapes): + """Restore the same value into all variables.""" + tensor, = restored_tensors + # To preserve the sum across save and restore, we have to divide the + # total across all devices when restoring a variable that was summed + # when saving. + if self._tower_local_variable.reduce_method == "sum": + tensor *= 1. / len(self._tower_local_variable.devices) + return control_flow_ops.group([ + _assign_on_device(d, v, tensor) + for d, v in six.iteritems(self._tower_local_variable._index)]) # pylint: disable=protected-access + + +class TowerLocalVariable(DistributedVariable, PerDevice, + checkpointable.CheckpointableBase): + """Holds a map from device to variables whose values are reduced on save.""" + + def __init__(self, index, primary_var, reduce_method): + self._primary_var = primary_var + self._reduce_method = reduce_method + super(TowerLocalVariable, self).__init__(index) + + def assign_sub(self, *args, **kwargs): + return self.get().assign_sub(*args, **kwargs) + + def assign_add(self, *args, **kwargs): + return self.get().assign_add(*args, **kwargs) + + def assign(self, *args, **kwargs): + return self.get().assign(*args, **kwargs) + + @property + def reduce_method(self): + return self._reduce_method + + def _gather_saveables_for_checkpoint(self): + """Overrides CheckpointableBase method. + + This allows both name-based and object-based save and restore of + TowerLocalVariables. + + Returns: + A dictionary mapping attribute names to `SaveableObject` factories. + """ + def _saveable_factory(name=self._common_name): + return _TowerLocalSaveable(self, name) + return {checkpointable.VARIABLE_VALUE_KEY: _saveable_factory} + + +def _devices_match(d1, d2): + return device_util.canonicalize(d1) == device_util.canonicalize(d2) + + +def regroup(per_device, wrap_class=PerDevice): + """Makes device->nest map into a nest of PerDevice/Mirrored values.""" + items = list(per_device.items()) + assert items + v0 = items[0][1] # First value + + if isinstance(v0, list): + for _, v in items[1:]: + assert isinstance(v, list) + assert len(v) == len(v0), ("len(v) == %d, len(v0) == %d, v: %s, v0: %s" % + (len(v), len(v0), v, v0)) + return [regroup({k: v[i] for k, v in items}, wrap_class) + for i in range(len(v0))] + + if isinstance(v0, tuple): + for _, v in items[1:]: + assert isinstance(v, tuple) + assert len(v) == len(v0) + regrouped_tuple = tuple(regroup({k: v[i] for k, v in items}, wrap_class) + for i in range(len(v0))) + if hasattr(v0, "_fields"): + # This tuple is in fact a namedtuple! Create a new namedtuple instance + # and initialize it with the regrouped values: + assert hasattr(type(v0), "_make") + return type(v0)._make(regrouped_tuple) + else: + return regrouped_tuple + + if isinstance(v0, dict): + v0keys = set(v0.keys()) + for _, v in items[1:]: + assert isinstance(v, dict) + assert set(v.keys()) == v0keys + return {key: regroup({k: v[key] for k, v in items}, wrap_class) + for key in v0keys} + + # If exactly the same object across all devices, return it unwrapped. + same_id = True + for _, v in items[1:]: + if v is not v0: + same_id = False + break + # Consider three cases where same_id is true: + # * If v0 is a MirroredVariable (and same_id means it is the same + # across all devices), we want to return it. We check + # MirroredVariable specifically since it can look like it + # has a _mirrored_container member since its members do. + # * If v0 is a member of a mirrored variable, in which case + # hasattr(v0, "_mirrored_container") is true, we want to + # return the MirroredVariable that contains it using the + # _mirrored_container logic below. This case can trigger + # same_id when there is only one device. + # * In any other situation, same_id means we return v0. + if same_id and (isinstance(v0, MirroredVariable) or + not hasattr(v0, "_mirrored_container")): + return v0 + + # Detect the case where each device has a parallel component of the + # same MirroredVariable. In this case we want to return the + # containing MirroredVariable, after a bunch of sanity checking. + # In particular, each component should have the same container, + # and the devices of the variables should match the keys of the + # per-device dictionary. + # TODO(josh11b): Do we need similar logic for TowerLocalVariables? + if hasattr(v0, "_mirrored_container"): + # pylint: disable=protected-access + assert not isinstance(v0, MirroredVariable), ( + "ids = %s, items = %s" % ([id(v[1]) for v in items], items)) + assert _devices_match(v0.device, items[0][0]), ( + "v0.device = %s, items = %s" % (v0.device, items)) + mirrored_container = v0._mirrored_container() + assert mirrored_container is not None + for d, v in items[1:]: + assert _devices_match(v.device, d), ( + "v.device = %s, d = %s, items = %s" % (v.device, d, items)) + assert mirrored_container is v._mirrored_container() + return mirrored_container + # pylint: enable=protected-access + + return wrap_class(per_device) + + +def select_device(device, structured): + """Specialize a nest of regular & per-device values for one device.""" + def _get(x): + return x.get(device) if isinstance(x, DistributedValues) else x + + return nest.map_structure(_get, structured) + + +def select_device_mirrored(device, structured): + """Specialize a nest of regular & mirrored values for one device.""" + def _get_mirrored(x): + if isinstance(x, DistributedValues): + if not isinstance(x, Mirrored): + raise TypeError( + "Expected value to be mirrored across towers: %s in %s." % + (x, structured)) + return x.get(device) + else: + return x + + return nest.map_structure(_get_mirrored, structured) + + +class PerDeviceDataIterator(object): + """An iterator (like `tf.data.Iterator`) into a `PerDeviceDataset`.""" + + def __init__(self, iterator, devices, prefetch_on_device=None): + self._iterator = iterator + self._devices = devices + self._prefetch_on_device = prefetch_on_device + + def get_next(self, name=None): + """Scatter the input across devices.""" + if self._prefetch_on_device: + data_list = self._iterator.get_next(name=name) + index = dict(zip(self._devices, data_list)) + else: + batch = self._iterator.get_next(name=name) + index = {} + def get_ith(i): + return lambda x: x[i] + + for i, d in enumerate(self._devices): + index[d] = nest.map_structure(get_ith(i), batch) + if context.executing_eagerly(): + with ops.device(d): + index[d] = nest.map_structure(array_ops.identity, index[d]) + + return regroup(index) + + +class PerDeviceDataset(object): + """Like `tf.data.Dataset` split devices, producing `PerDevice` data.""" + + def __init__(self, dataset, devices, prefetch_on_device=None): + self._devices = devices + + # Default to using prefetching in graph mode, unless specified. + # TODO(priyag): Enable prefetching in eager mode. + self._prefetch_on_device = prefetch_on_device + if self._prefetch_on_device is None: + self._prefetch_on_device = not context.executing_eagerly() + assert not (self._prefetch_on_device and context.executing_eagerly()), ( + "Prefetching is only supported in graph mode currently") + + if self._prefetch_on_device: + self._dataset = dataset + else: + # TODO(priyag): If dropping remainder is not appropriate, find another + # approach to distributing the dataset when not possible to divide evenly. + # Possibly not an issue when we start using PartitionedDataset. + self._dataset = dataset.apply( + batching.batch_and_drop_remainder(len(devices))) + + def make_one_shot_iterator(self): + """Get a one time use iterator for the distributed PerDeviceDataset.""" + if self._prefetch_on_device: + on_device_dataset = self._dataset.apply( + prefetching_ops_v2.prefetch_to_devices(self._devices)) + dataset_iterator = on_device_dataset.make_one_shot_iterator() + elif context.executing_eagerly(): + dataset_iterator = datasets.Iterator(self._dataset) + else: + dataset_iterator = self._dataset.make_one_shot_iterator() + + return PerDeviceDataIterator( + dataset_iterator, self._devices, self._prefetch_on_device) + + +class MapOutput(object): + """Map can result in multiple outputs per device.""" + + def __init__(self, l): + self._l = l + + def get(self): + return self._l diff --git a/tensorflow/contrib/distribute/python/values_test.py b/tensorflow/contrib/distribute/python/values_test.py new file mode 100644 index 0000000000..5c0d4b7d6c --- /dev/null +++ b/tensorflow/contrib/distribute/python/values_test.py @@ -0,0 +1,807 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for the distributed values library.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from tensorflow.contrib.distribute.python import mirrored_strategy +from tensorflow.contrib.distribute.python import values +from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import errors +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.training import device_util +from tensorflow.python.training import saver as saver_lib + + +@test_util.with_c_api +class DistributedValuesTest(test.TestCase): + + def testGetEager(self): + with ops.device("/device:CPU:0"): + one = constant_op.constant(1) + two = constant_op.constant(2) + v = values.DistributedValues({"/device:CPU:0": one, "/device:GPU:0": two}) + self.assertEqual(two, v.get("/device:GPU:0")) + self.assertEqual(one, v.get()) + with self.assertRaises(ValueError): + self.assertIsNone(v.get("/device:GPU:2")) + + def testGetGraph(self): + with context.graph_mode(), \ + ops.Graph().as_default(), \ + ops.device("/device:CPU:0"): + one = constant_op.constant(1) + two = constant_op.constant(2) + v = values.DistributedValues({"/device:CPU:0": one, "/device:GPU:0": two}) + self.assertEqual(two, v.get("/device:GPU:0")) + self.assertEqual(one, v.get()) + with self.assertRaises(ValueError): + self.assertIsNone(v.get("/device:GPU:2")) + + def testCanonicalization(self): + canonical_cpu = ["/job:localhost/replica:0/task:0/device:CPU:0"] + v = values.DistributedValues({"": 42}) + self.assertEqual(canonical_cpu, list(v._index.keys())) + v = values.DistributedValues({"/device:CPU:0": 42}) + self.assertEqual(canonical_cpu, list(v._index.keys())) + v = values.DistributedValues({"/cpu:0": 42}) + self.assertEqual(canonical_cpu, list(v._index.keys())) + v = values.DistributedValues({"/CPU:0": 42}) + self.assertEqual(canonical_cpu, list(v._index.keys())) + with self.assertRaises(AssertionError): + v = values.DistributedValues({"/device:cpu:0": 42}) + + +@test_util.with_c_api +class DistributedDelegateTest(test.TestCase): + + @test_util.run_in_graph_and_eager_modes() + def testGetAttr(self): + with ops.device("/device:CPU:0"): + + class Foo(object): + + def __init__(self, x): + self.x = x + + v = values.DistributedDelegate( + {"/device:CPU:0": Foo(7), "/device:GPU:0": Foo(8)}) + self.assertEqual(7, v.x) + with self.assertRaises(AttributeError): + _ = v.y + + @test_util.run_in_graph_and_eager_modes() + def testOperatorOverride(self): + with ops.device("/device:CPU:0"): + v = values.DistributedDelegate({"/device:CPU:0": 7, "/device:GPU:0": 8}) + # v should act like int(7). + self.assertEqual(8, v + 1) + self.assertEqual(10, 3 + v) + self.assertEqual(14, v + v) + self.assertEqual(5, v - 2) + self.assertEqual(6, 13 - v) + self.assertEqual(0, v - v) + self.assertEqual(14, v * 2) + self.assertEqual(21, 3 * v) + self.assertEqual(49, v * v) + self.assertEqual(3.5, v / 2) + self.assertEqual(1.5, 10.5 / v) + self.assertEqual(3, v // 2) + self.assertEqual(2, 15 // v) + self.assertEqual(1, v % 2) + self.assertEqual(2, 16 % v) + self.assertTrue(v < 12) + self.assertTrue(v <= 12) + self.assertFalse(v > 12) + self.assertFalse(v >= 12) + self.assertFalse(12 < v) + self.assertFalse(12 <= v) + self.assertTrue(12 > v) + self.assertTrue(12 >= v) + self.assertEqual(3, v & 3) + self.assertEqual(3, 11 & v) + self.assertEqual(15, v | 8) + self.assertEqual(23, 16 | v) + self.assertEqual(4, v ^ 3) + self.assertEqual(12, 11 ^ v) + self.assertEqual(343, pow(v, 3)) + self.assertEqual(3, pow(v, 3, 10)) + self.assertEqual(128, pow(2, v)) + self.assertEqual(-7, -v) + self.assertEqual(~7, ~v) + self.assertEqual(7, abs(v)) + with self.assertRaises(TypeError): + _ = v[2] + + +def _device_str(d): + return "/device:GPU:" + str(d) + + +def _nested_value(d): + return ("a" + d, ["b" + d, {"c": "d" + d, "e": "f" + d}, "g" + d], "h" + d) + + +def _make_mirrored(): + v = [] + index = {} + devices = ["/device:GPU:0", "/device:CPU:0"] + for d, n, init in zip(devices, ["v", "v/replica"], [1., 2.]): + with ops.device(d): + v.append(variable_scope.get_variable( + name=n, initializer=init, use_resource=True)) + index[d] = v[-1] + mirrored = values.MirroredVariable(index, v[0]) + return v, devices, mirrored + + +@test_util.with_c_api +class RegroupAndSelectDeviceTest(test.TestCase): + + def _is_per_device(self, result, expected, klass=values.PerDevice): + self.assertIsInstance(result, klass) + # We canonicalize the devices to match the device strings returned + # by PerDevice, which also does device string canonicalization. + devices = [device_util.canonicalize(_device_str(i)) + for i in range(len(expected))] + self.assertEqual(set(devices), set(result.devices)) + for i, d in enumerate(devices): + self.assertEqual(expected[i], result.get(d)) + self.assertEqual(expected[i], result.get(_device_str(i))) + + def testNested(self): + result = values.regroup({_device_str(0): _nested_value("1"), + _device_str(1): _nested_value("2")}) + self.assertIsInstance(result, tuple) + self.assertEqual(3, len(result)) + self._is_per_device(result[0], ["a1", "a2"]) + self._is_per_device(result[2], ["h1", "h2"]) + + self.assertIsInstance(result[1], list) + self.assertEqual(3, len(result[1])) + self._is_per_device(result[1][0], ["b1", "b2"]) + self._is_per_device(result[1][2], ["g1", "g2"]) + + self.assertIsInstance(result[1][1], dict) + self.assertEqual(set(["c", "e"]), set(result[1][1].keys())) + self._is_per_device(result[1][1]["c"], ["d1", "d2"]) + self._is_per_device(result[1][1]["e"], ["f1", "f2"]) + + # Also test that we can undo the merge using select_device() + self.assertEqual(_nested_value("1"), + values.select_device(_device_str(0), result)) + self.assertEqual(_nested_value("2"), + values.select_device(_device_str(1), result)) + # select_device_mirrored() should fail due to non-mirrored values + with self.assertRaises(TypeError): + values.select_device_mirrored(_device_str(0), result) + with self.assertRaises(TypeError): + values.select_device_mirrored(_device_str(1), result) + + def testWrapClass(self): + # Normally a mirrored value would be the same across devices, but + # for a test it is convenient to be able to tell the values apart. + result = values.regroup({_device_str(0): _nested_value("1"), + _device_str(1): _nested_value("2")}, + values.Mirrored) + self.assertIsInstance(result, tuple) + self.assertEqual(3, len(result)) + self._is_per_device(result[0], ["a1", "a2"], values.Mirrored) + self._is_per_device(result[2], ["h1", "h2"], values.Mirrored) + + self.assertIsInstance(result[1], list) + self.assertEqual(3, len(result[1])) + self._is_per_device(result[1][0], ["b1", "b2"], values.Mirrored) + self._is_per_device(result[1][2], ["g1", "g2"], values.Mirrored) + + self.assertIsInstance(result[1][1], dict) + self.assertEqual(set(["c", "e"]), set(result[1][1].keys())) + self._is_per_device(result[1][1]["c"], ["d1", "d2"], values.Mirrored) + self._is_per_device(result[1][1]["e"], ["f1", "f2"], values.Mirrored) + + # Also test that we can undo the merge using select_device() + self.assertEqual(_nested_value("1"), + values.select_device(_device_str(0), result)) + self.assertEqual(_nested_value("2"), + values.select_device(_device_str(1), result)) + # Values are marked as mirrored, so select_device_mirrored() is allowed. + self.assertEqual(_nested_value("1"), + values.select_device_mirrored(_device_str(0), result)) + self.assertEqual(_nested_value("2"), + values.select_device_mirrored(_device_str(1), result)) + + def testMirroredContainer(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + v, devices, mirrored = _make_mirrored() + result = values.regroup(dict(zip(devices, v))) + self.assertIs(mirrored, result) + + def testSameId(self): + foo = object() + result = values.regroup({_device_str(0): ("a", foo), + _device_str(1): ("b", foo)}) + self.assertIsInstance(result, tuple) + self.assertEqual(2, len(result)) + self._is_per_device(result[0], ["a", "b"]) + self.assertIs(foo, result[1]) + + # Test select_device(), should undo the merge done by regroup(). + result_0 = values.select_device(_device_str(0), result) + self.assertIsInstance(result_0, tuple) + self.assertEqual(2, len(result_0)) + self.assertEqual("a", result_0[0]) + self.assertIs(foo, result_0[1]) + result_1 = values.select_device(_device_str(1), result) + self.assertIsInstance(result_1, tuple) + self.assertEqual(2, len(result_1)) + self.assertEqual("b", result_1[0]) + self.assertIs(foo, result_1[1]) + + def testOneDevice(self): + result = values.regroup({_device_str(0): _nested_value("1")}) + # On one device regroup() and select_device() are basically identity. + self.assertEqual(_nested_value("1"), result) + self.assertEqual(_nested_value("1"), + values.select_device(_device_str(0), result)) + + # The one exception has to do with MirroredVariables. + d = "/device:CPU:0" + with ops.device(d): + v = variable_scope.get_variable( + name="v", initializer=1., use_resource=True) + index = {d: v} + mirrored = values.MirroredVariable(index, v) + result = values.regroup(index) + self.assertIs(mirrored, result) + + def testNamedTupleEstimatorSpec(self): + with context.graph_mode(), ops.Graph().as_default(): + created_estimator_specs = {} + to_regroup = {} + + for device_id in range(3): + spec = model_fn_lib.EstimatorSpec( + mode=model_fn_lib.ModeKeys.TRAIN, + loss=constant_op.constant(device_id / 2), + train_op=array_ops.identity(constant_op.constant(device_id))) + created_estimator_specs[device_id] = spec + to_regroup[_device_str(device_id)] = spec + + merged_estimator_spec = values.regroup(to_regroup) + + self.assertTrue( + isinstance(merged_estimator_spec, model_fn_lib.EstimatorSpec)) + self.assertEquals(model_fn_lib.ModeKeys.TRAIN, merged_estimator_spec.mode) + for device_id in range(3): + d = _device_str(device_id) + self.assertEquals(created_estimator_specs[device_id].loss, + merged_estimator_spec.loss.get(d)) + self.assertEquals(created_estimator_specs[device_id].train_op, + merged_estimator_spec.train_op.get(d)) + # Scaffold is populated by `EstimatorSpec.__new__`. + self.assertEquals(created_estimator_specs[device_id].scaffold, + merged_estimator_spec.scaffold.get(d)) + # Also test that we can undo the merge using select_device() + self.assertEquals(created_estimator_specs[device_id], + values.select_device(_device_str(device_id), + merged_estimator_spec)) + + +@test_util.with_c_api +class PerDeviceDatasetTest(test.TestCase): + + config = config_pb2.ConfigProto() + config.allow_soft_placement = True + + def _test_iterator_no_prefetch(self, devices, dataset, expected_values): + per_device_dataset = values.PerDeviceDataset( + dataset, devices, prefetch_on_device=False) + iterator = per_device_dataset.make_one_shot_iterator() + + for expected_value in expected_values: + next_element = iterator.get_next() + actual = self.evaluate([ + values.select_device(d, next_element) for d in devices]) + self.assertEqual(expected_value, actual) + + with self.assertRaises(errors.OutOfRangeError): + next_element = iterator.get_next() + self.evaluate([ + values.select_device(d, next_element) for d in devices]) + + def _test_iterator_with_prefetch(self, devices, dataset, expected_values): + if not context.executing_eagerly(): + per_device_dataset = values.PerDeviceDataset( + dataset, devices, prefetch_on_device=True) + iterator = per_device_dataset.make_one_shot_iterator() + + # With prefetching, we cannot guarantee which input ends up on which + # device, so we verify that the complete set seen on all devices is + # correct, and equal numbers are distributed to each device. + combined_actual = [] + combined_expected = [] + for expected_value in expected_values: + next_element = iterator.get_next() + combined_actual.extend(self.evaluate([ + values.select_device(d, next_element) for d in devices])) + combined_expected.extend(expected_value) + + self.assertEqual(set(combined_expected), set(combined_actual)) + + with self.assertRaises(errors.OutOfRangeError): + next_element = iterator.get_next() + self.evaluate([ + values.select_device(d, next_element) for d in devices]) + + def _test_iterator(self, devices, dataset, expected_values): + self._test_iterator_no_prefetch(devices, dataset, expected_values) + self._test_iterator_with_prefetch(devices, dataset, expected_values) + + @test_util.run_in_graph_and_eager_modes() + def testOneDevice(self): + devices = ["/device:CPU:0"] + dataset = dataset_ops.Dataset.range(10) + + expected_values = [[i] for i in range(10)] + + self._test_iterator(devices, dataset, expected_values) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testMultipleDevices(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + devices = ["/device:CPU:0", "/device:GPU:0"] + dataset = dataset_ops.Dataset.range(10) + + expected_values = [[i, i+1] for i in range(0, 10, 2)] + + self._test_iterator(devices, dataset, expected_values) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testTupleDataset(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + devices = ["/device:CPU:0", "/device:GPU:0"] + dataset1 = dataset_ops.Dataset.range(10) + dataset2 = dataset_ops.Dataset.range(10).map(lambda x: x**2) + dataset = dataset_ops.Dataset.zip((dataset1, dataset2)) + + expected_values = [[(i, i**2), (i+1, (i+1)**2)] for i in range(0, 10, 2)] + + self._test_iterator(devices, dataset, expected_values) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testUnevenDatasetBatches(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + devices = ["/device:CPU:0", "/device:GPU:0"] + dataset = dataset_ops.Dataset.range(11) + + expected_values = [[i, i+1] for i in range(0, 10, 2)] + self._test_iterator(devices, dataset, expected_values) + + +@test_util.with_c_api +class MirroredVariableTest(test.TestCase): + + config = config_pb2.ConfigProto() + config.allow_soft_placement = True + + @test_util.run_in_graph_and_eager_modes(config=config) + def testProperties(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + v, _, mirrored = _make_mirrored() + + self.assertEquals(v[0].name, mirrored.name) + self.assertEquals(v[0].dtype, mirrored.dtype) + self.assertEquals(v[0].shape, mirrored.shape) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testVariableOnAnotherDevice(self): + v = variable_scope.get_variable( + name="v", initializer=[1.], use_resource=True) + index = {"/job:foo/device:CPU:0": v} + mirrored = values.MirroredVariable(index, v) + + self.assertEquals(v.name, mirrored.name) + self.assertEquals(v.dtype, mirrored.dtype) + self.assertEquals(v.shape, mirrored.shape) + + def _assign_mirrored(self, devices, v, new): + for d, var, n in zip(devices, v, new): + with ops.device(d): + self.evaluate(var.assign(n)) + + def _save_return_saver(self, sess, var): + saver = saver_lib.Saver(var_list=[var]) + test_dir = self.get_temp_dir() + prefix = os.path.join(test_dir, "ckpt") + return saver.save(sess, prefix), saver + + def _save(self, sess, var): + save_path, _ = self._save_return_saver(sess, var) + return save_path + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveAndRestoreMirroredOneGraph(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + with self.test_session() as sess: + v, devices, mirrored = _make_mirrored() + + # Overwrite the initial values. + self._assign_mirrored(devices, v, [3., 4.]) + + # Saves the current value of v[0], 3. + save_path, saver = self._save_return_saver(sess, mirrored) + + # Change the values between save and restore. + self._assign_mirrored(devices, v, [5., 6.]) + + # Restores the saved value of 3. to both variables. + saver.restore(sess, save_path) + self.assertEqual([3., 3.], self.evaluate([v[0], v[1]])) + + def _save_mirrored(self): + """Save variables with mirroring, returns save_path.""" + with self.test_session(graph=ops.Graph()) as sess: + v, devices, mirrored = _make_mirrored() + + # Overwrite the initial values. + self._assign_mirrored(devices, v, [3., 4.]) + + # Saves the current value of v[0], 3. + save_path = self._save(sess, mirrored) + + # Change the values between save and restore. + self._assign_mirrored(devices, v, [5., 6.]) + return save_path + + def _save_normal(self): + """Save variables without mirroring, returns save_path.""" + with self.test_session(graph=ops.Graph()) as sess: + var = variable_scope.get_variable( + name="v", initializer=1., use_resource=True) + + # Overwrite the initial value. + self.evaluate(var.assign(3.)) + + # Saves the current value of var, 3. + save_path = self._save(sess, var) + + # Change the values between save and restore. + self.evaluate(var.assign(5.)) + return save_path + + def _restore_normal(self, save_path): + """Restore to variables without mirroring in a fresh graph.""" + with self.test_session(graph=ops.Graph()) as sess: + var = variable_scope.get_variable( + name="v", initializer=7., use_resource=True) + + # Overwrite the initial value. + self.evaluate(var.assign(8.)) + + # Restores the saved value of 3. to `var`. + saver = saver_lib.Saver(var_list=[var]) + saver.restore(sess, save_path) + self.assertEqual(3., self.evaluate(var)) + + def _restore_mirrored(self, save_path): + """Restore to variables with mirroring in a fresh graph.""" + with self.test_session(graph=ops.Graph()) as sess: + v, devices, mirrored = _make_mirrored() + + # Overwrite the initial values. + self._assign_mirrored(devices, v, [7., 8.]) + + # Restores the saved value of 3. to both variables. + saver = saver_lib.Saver(var_list=[mirrored]) + saver.restore(sess, save_path) + self.assertEqual([3., 3.], self.evaluate([v[0], v[1]])) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveMirroredRestoreMirrored(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_mirrored() + self._restore_mirrored(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveMirroredRestoreNormal(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_mirrored() + self._restore_normal(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveNormalRestoreMirrored(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_normal() + self._restore_mirrored(save_path) + + +_devices = ["/device:GPU:0", "/device:CPU:0"] + + +def _make_tower_local(method): + v = [] + index = {} + for d, n, init in zip(_devices, ["v", "v/replica"], [1., 2.]): + with ops.device(d): + v.append(variable_scope.get_variable( + name=n, initializer=init, use_resource=True)) + index[d] = v[-1] + tower_local = values.TowerLocalVariable(index, v[0], method) + return v, tower_local + + +@test_util.with_c_api +class TowerLocalVariableTest(test.TestCase): + + config = config_pb2.ConfigProto() + config.allow_soft_placement = True + + @test_util.run_in_graph_and_eager_modes(config=config) + def testProperties(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + v, tower_local = _make_tower_local("sum") + + self.assertEquals(v[0].name, tower_local.name) + self.assertEquals(v[0].dtype, tower_local.dtype) + self.assertEquals(v[0].shape, tower_local.shape) + self.assertEquals("sum", tower_local.reduce_method) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testVariableOnAnotherDevice(self): + v = variable_scope.get_variable( + name="v", initializer=[1.], use_resource=True) + index = {"/job:foo/device:CPU:0": v} + tower_local = values.TowerLocalVariable(index, v, "mean") + + self.assertEquals(v.name, tower_local.name) + self.assertEquals(v.dtype, tower_local.dtype) + self.assertEquals(v.shape, tower_local.shape) + self.assertEquals("mean", tower_local.reduce_method) + + def _assign_tower_local(self, devices, v, new): + for d, var, n in zip(devices, v, new): + with ops.device(d): + self.evaluate(var.assign(n)) + + def _save_return_saver(self, sess, var): + saver = saver_lib.Saver(var_list=[var]) + test_dir = self.get_temp_dir() + prefix = os.path.join(test_dir, "ckpt") + return saver.save(sess, prefix), saver + + def _save(self, sess, var): + save_path, _ = self._save_return_saver(sess, var) + return save_path + + def _dist_scope(self): + return mirrored_strategy.MirroredStrategy(_devices).scope() + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveAndRestoreTowerLocalSumOneGraph(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + with self.test_session() as sess: + v, tower_local = _make_tower_local("sum") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [3., 4.]) + + with self._dist_scope(): + # Saves the current value of v[0] + v[1], 7. + save_path, saver = self._save_return_saver(sess, tower_local) + + # Change the values between save and restore. + self._assign_tower_local(_devices, v, [5., 6.]) + + # Restores the saved value of 7. which gets divided equally + # between the variables. + saver.restore(sess, save_path) + self.assertEqual([3.5, 3.5], self.evaluate([v[0], v[1]])) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveAndRestoreTowerLocalMeanOneGraph(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + with self.test_session() as sess: + v, tower_local = _make_tower_local("mean") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [3., 4.]) + + with self._dist_scope(): + # Saves the current value of (v[0] + v[1])/2, 3.5. + save_path, saver = self._save_return_saver(sess, tower_local) + + # Change the values between save and restore. + self._assign_tower_local(_devices, v, [5., 6.]) + + # Restores the saved value of 3.5 to both variables. + saver.restore(sess, save_path) + self.assertEqual([3.5, 3.5], self.evaluate([v[0], v[1]])) + + def _save_tower_local_mean(self): + """Save variables with mirroring, returns save_path.""" + with self.test_session(graph=ops.Graph()) as sess: + v, tower_local = _make_tower_local("mean") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [3., 4.]) + + with self._dist_scope(): + # Saves the current value of (v[0] + v[1])/2, 3.5 + save_path = self._save(sess, tower_local) + + # Change the values between save and restore. + self._assign_tower_local(_devices, v, [5., 6.]) + return save_path + + def _save_tower_local_sum(self): + """Save variables with mirroring, returns save_path.""" + with self.test_session(graph=ops.Graph()) as sess: + v, tower_local = _make_tower_local("sum") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [1.5, 2.]) + + with self._dist_scope(): + # Saves the current value of v[0] + v[1], 3.5 + save_path = self._save(sess, tower_local) + + # Change the values between save and restore. + self._assign_tower_local(_devices, v, [5., 6.]) + return save_path + + def _save_normal(self): + """Save variables without mirroring, returns save_path.""" + with self.test_session(graph=ops.Graph()) as sess: + var = variable_scope.get_variable( + name="v", initializer=1., use_resource=True) + + # Overwrite the initial value. + self.evaluate(var.assign(3.5)) + + # Saves the current value of var, 3.5. + save_path = self._save(sess, var) + + # Change the values between save and restore. + self.evaluate(var.assign(5.)) + return save_path + + def _restore_normal(self, save_path): + """Restore to variables without mirroring in a fresh graph.""" + with self.test_session(graph=ops.Graph()) as sess: + var = variable_scope.get_variable( + name="v", initializer=7., use_resource=True) + + # Overwrite the initial value. + self.evaluate(var.assign(8.)) + + # Restores the saved value of 3.5 to `var`. + saver = saver_lib.Saver(var_list=[var]) + saver.restore(sess, save_path) + self.assertEqual(3.5, self.evaluate(var)) + + def _restore_tower_local_mean(self, save_path): + """Restore to variables with mirroring in a fresh graph.""" + with self.test_session(graph=ops.Graph()) as sess: + v, tower_local = _make_tower_local("mean") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [7., 8.]) + + with self._dist_scope(): + # Restores the saved value of 3.5 to both variables. + saver = saver_lib.Saver(var_list=[tower_local]) + saver.restore(sess, save_path) + self.assertEqual([3.5, 3.5], self.evaluate([v[0], v[1]])) + + def _restore_tower_local_sum(self, save_path): + """Restore to variables with mirroring in a fresh graph.""" + with self.test_session(graph=ops.Graph()) as sess: + v, tower_local = _make_tower_local("sum") + + # Overwrite the initial values. + self._assign_tower_local(_devices, v, [7., 8.]) + + with self._dist_scope(): + # Restores the saved value of 3.5 to both variables. + saver = saver_lib.Saver(var_list=[tower_local]) + saver.restore(sess, save_path) + self.assertEqual([1.75, 1.75], self.evaluate([v[0], v[1]])) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveTowerLocalRestoreTowerLocalMean(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_tower_local_mean() + self._restore_tower_local_mean(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveTowerLocalRestoreTowerLocalSum(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_tower_local_sum() + self._restore_tower_local_sum(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveTowerLocalMeanRestoreNormal(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_tower_local_mean() + self._restore_normal(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveTowerLocalSumRestoreNormal(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_tower_local_sum() + self._restore_normal(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveNormalRestoreTowerLocalMean(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_normal() + self._restore_tower_local_mean(save_path) + + @test_util.run_in_graph_and_eager_modes(config=config) + def testSaveNormalRestoreTowerLocalSum(self): + if context.num_gpus() < 1 and context.executing_eagerly(): + self.skipTest("A GPU is not available for this test in eager mode.") + + save_path = self._save_normal() + self._restore_tower_local_sum(save_path) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/BUILD b/tensorflow/contrib/optimizer_v2/BUILD new file mode 100644 index 0000000000..26ea9135f5 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/BUILD @@ -0,0 +1,205 @@ +# Prototype of OptimizerV2. + +package( + default_visibility = ["//tensorflow:internal"], +) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("//tensorflow:tensorflow.bzl", "py_test") +load("//tensorflow:tensorflow.bzl", "cuda_py_test") + +filegroup( + name = "all_files", + srcs = glob( + ["**/*"], + exclude = [ + "**/METADATA", + "**/OWNERS", + ], + ), + visibility = ["//tensorflow:__subpackages__"], +) + +py_library( + name = "optimizer_v2_py", + srcs = ["optimizer_v2_symbols.py"], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], + deps = [ + ":training", + "//tensorflow/python:util", + ], +) + +py_library( + name = "training", + srcs = [ + "adadelta.py", + "adagrad.py", + "adam.py", + "gradient_descent.py", + "momentum.py", + "optimizer_v2.py", + "rmsprop.py", + ], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + ], +) + +cuda_py_test( + name = "adadelta_test", + size = "medium", + srcs = ["adadelta_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:client_testlib", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:variables", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "adagrad_test", + size = "small", + srcs = ["adagrad_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "adam_test", + size = "small", + srcs = ["adam_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "checkpointable_utils_test", + srcs = ["checkpointable_utils_test.py"], + additional_deps = [ + ":training", + "@six_archive//:six", + "//tensorflow/contrib/eager/python:checkpointable_utils", + "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:init_ops", + "//tensorflow/python:layers", + "//tensorflow/python:layers_base", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "//tensorflow/python/keras", + ], + tags = ["notsan"], +) + +cuda_py_test( + name = "gradient_descent_test", + size = "medium", + srcs = ["gradient_descent_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:client_testlib", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:resources", + "//tensorflow/python:variables", + ], +) + +cuda_py_test( + name = "momentum_test", + size = "medium", + srcs = ["momentum_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:client_testlib", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:resources", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + ], +) + +cuda_py_test( + name = "optimizer_v2_test", + size = "medium", + srcs = ["optimizer_v2_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:array_ops", + "//tensorflow/python:clip_ops", + "//tensorflow/python:gradients", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:variables", + ], +) + +cuda_py_test( + name = "rmsprop_test", + size = "small", + srcs = ["rmsprop_test.py"], + additional_deps = [ + ":training", + "//tensorflow/python:array_ops", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], +) diff --git a/tensorflow/contrib/optimizer_v2/adadelta.py b/tensorflow/contrib/optimizer_v2/adadelta.py new file mode 100644 index 0000000000..b206f9f61b --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adadelta.py @@ -0,0 +1,113 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Adadelta for TensorFlow.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.training import training_ops + + +class AdadeltaOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the Adadelta algorithm. + + See [M. D. Zeiler](http://arxiv.org/abs/1212.5701) + ([pdf](http://arxiv.org/pdf/1212.5701v1.pdf)) + """ + + def __init__(self, learning_rate=0.001, rho=0.95, epsilon=1e-8, + use_locking=False, name="Adadelta"): + """Construct a new Adadelta optimizer. + + Some of the args below are hyperparameters, where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate. + To match the exact form in the original paper use 1.0. + rho: A float hyperparameter. The decay rate. + epsilon: A float hyperparameter. A constant epsilon used to better + condition the grad update. + use_locking: If `True` use locks for update operations. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "Adadelta". + """ + super(AdadeltaOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("rho", rho) + self._set_hyper("epsilon", epsilon) + + def _create_vars(self, var_list, state): + for v in var_list: + state.zeros_slot(v, "accum") + state.zeros_slot(v, "accum_update") + + def _apply_dense(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.apply_adadelta( + var, + accum, + accum_update, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _resource_apply_dense(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.resource_apply_adadelta( + var.handle, + accum.handle, + accum_update.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.sparse_apply_adadelta( + var, + accum, + accum_update, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.resource_sparse_apply_adadelta( + var.handle, + accum.handle, + accum_update.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/adadelta_test.py b/tensorflow/contrib/optimizer_v2/adadelta_test.py new file mode 100644 index 0000000000..31cfec0d50 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adadelta_test.py @@ -0,0 +1,167 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for Adadelta Optimizer.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.optimizer_v2 import adadelta +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.ops import embedding_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class AdadeltaOptimizerTest(test.TestCase): + + def doTestBasic(self, use_resource=False): + num_updates = 4 # number of ADADELTA steps to perform + for dtype in [dtypes.half, dtypes.float32]: + for grad in [0.2, 0.1, 0.01]: + for lr in [1.0, 0.5, 0.1]: + with self.test_session(): + var0_init = [1.0, 2.0] + var1_init = [3.0, 4.0] + if use_resource: + var0 = resource_variable_ops.ResourceVariable( + var0_init, dtype=dtype) + var1 = resource_variable_ops.ResourceVariable( + var1_init, dtype=dtype) + else: + var0 = variables.Variable(var0_init, dtype=dtype) + var1 = variables.Variable(var1_init, dtype=dtype) + + grads = constant_op.constant([grad, grad], dtype=dtype) + + accum = 0.0 + accum_update = 0.0 + + # ADADELTA gradient optimizer + rho = 0.95 + epsilon = 1e-8 + adadelta_opt = adadelta.AdadeltaOptimizer(lr, rho, epsilon) + adadelta_update = adadelta_opt.apply_gradients( + zip([grads, grads], [var0, var1])) + + opt_vars = adadelta_opt.variables() + self.assertStartsWith(opt_vars[0].name, var0._shared_name) + self.assertStartsWith(opt_vars[1].name, var0._shared_name) + self.assertStartsWith(opt_vars[2].name, var1._shared_name) + self.assertStartsWith(opt_vars[3].name, var1._shared_name) + self.assertEqual(4, len(opt_vars)) + + variables.global_variables_initializer().run() + + # Assign slots + slot = [None] * 2 + slot_update = [None] * 2 + self.assertEqual(["accum", "accum_update"], + adadelta_opt.get_slot_names()) + slot[0] = adadelta_opt.get_slot(var0, "accum") + self.assertEquals(slot[0].get_shape(), var0.get_shape()) + self.assertFalse(slot[0] in variables.trainable_variables()) + + slot_update[0] = adadelta_opt.get_slot(var0, "accum_update") + self.assertEquals(slot_update[0].get_shape(), var0.get_shape()) + self.assertFalse(slot_update[0] in variables.trainable_variables()) + + slot[1] = adadelta_opt.get_slot(var1, "accum") + self.assertEquals(slot[1].get_shape(), var1.get_shape()) + self.assertFalse(slot[1] in variables.trainable_variables()) + + slot_update[1] = adadelta_opt.get_slot(var1, "accum_update") + self.assertEquals(slot_update[1].get_shape(), var1.get_shape()) + self.assertFalse(slot_update[1] in variables.trainable_variables()) + + # Fetch params to validate initial values + self.assertAllClose(var0_init, var0.eval()) + self.assertAllClose(var1_init, var1.eval()) + + update = [None] * num_updates + tot_update = 0 + for step in range(num_updates): + # Run adadelta update for comparison + adadelta_update.run() + + # Perform initial update without previous accum values + accum = accum * rho + (grad**2) * (1 - rho) + update[step] = (np.sqrt(accum_update + epsilon) * + (1. / np.sqrt(accum + epsilon)) * grad) + accum_update = (accum_update * rho + (update[step]**2) * + (1.0 - rho)) + tot_update += update[step] * lr + + # Check that the accumulators have been updated + for slot_idx in range(2): + self.assertAllCloseAccordingToType( + np.array([accum, accum], dtype=dtype.as_numpy_dtype()), + slot[slot_idx].eval(), + rtol=1e-5) + + self.assertAllCloseAccordingToType( + np.array( + [accum_update, accum_update], + dtype=dtype.as_numpy_dtype()), + slot_update[slot_idx].eval(), + rtol=1e-5) + + # Check that the parameters have been updated + self.assertAllCloseAccordingToType( + np.array( + [var0_init[0] - tot_update, var0_init[1] - tot_update], + dtype=dtype.as_numpy_dtype()), + var0.eval(), + rtol=1e-5) + + self.assertAllCloseAccordingToType( + np.array( + [var1_init[0] - tot_update, var1_init[1] - tot_update], + dtype=dtype.as_numpy_dtype()), + var1.eval(), + rtol=1e-5) + + def testBasic(self): + self.doTestBasic(use_resource=False) + + def testResourceBasic(self): + self.doTestBasic(use_resource=True) + + def testMinimizeSparseResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + loss = pred * pred + sgd_op = adadelta.AdadeltaOptimizer( + 1.0, 1.0, 1.0).minimize(loss) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0, 2.0]], var0.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType( + [[-111, -138]], var0.eval()) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/adagrad.py b/tensorflow/contrib/optimizer_v2/adagrad.py new file mode 100644 index 0000000000..e54f990cca --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adagrad.py @@ -0,0 +1,118 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Adagrad optimizer for TensorFlow.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import ops +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.training import training_ops + + +class AdagradOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the Adagrad algorithm. + + See this [paper](http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf) + or this + [intro](http://cs.stanford.edu/~ppasupat/a9online/uploads/proximal_notes.pdf). + """ + + def __init__(self, learning_rate, initial_accumulator_value=0.1, + use_locking=False, name="Adagrad"): + """Construct a new Adagrad optimizer. + + The learning_rate arg below is a hyperparameter, where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate. + initial_accumulator_value: A floating point value. + Starting value for the accumulators, must be positive. + use_locking: If `True` use locks for update operations. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "Adagrad". + + Raises: + ValueError: If the `initial_accumulator_value` is invalid. + """ + if initial_accumulator_value <= 0.0: + raise ValueError("initial_accumulator_value must be positive: %s" % + initial_accumulator_value) + super(AdagradOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + + self._initial_accumulator_value = initial_accumulator_value + + def _create_vars(self, var_list, state): + for v in var_list: + with ops.colocate_with(v): + dtype = v.dtype.base_dtype + if v.get_shape().is_fully_defined(): + init = init_ops.constant_initializer(self._initial_accumulator_value, + dtype=dtype) + else: + # Use a Tensor instead of initializer if variable does not have static + # shape. + init_constant = gen_array_ops.fill( + array_ops.shape(v), self._initial_accumulator_value) + init = math_ops.cast(init_constant, dtype) + state.create_slot_with_initializer(v, init, v.get_shape(), dtype, + "accumulator") + + def _apply_dense(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.apply_adagrad( + var, + acc, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _resource_apply_dense(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.resource_apply_adagrad( + var.handle, + acc.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.sparse_apply_adagrad( + var, + acc, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + acc = state.get_slot(var, "accumulator") + return training_ops.resource_sparse_apply_adagrad( + var.handle, + acc.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/adagrad_test.py b/tensorflow/contrib/optimizer_v2/adagrad_test.py new file mode 100644 index 0000000000..18191c3ef2 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adagrad_test.py @@ -0,0 +1,282 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Functional tests for aggregate operations.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.optimizer_v2 import adagrad +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import embedding_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class AdagradOptimizerTest(test.TestCase): + + def doTestBasic(self, use_locking=False, use_resource=False): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + if use_resource: + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype) + else: + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + ada_opt = adagrad.AdagradOptimizer( + 3.0, initial_accumulator_value=0.1, use_locking=use_locking) + ada_update = ada_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Run 3 steps of adagrad + for _ in range(3): + ada_update.run() + # Validate updated params + self.assertAllCloseAccordingToType( + np.array([-1.6026098728179932, -0.6026098728179932]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([2.715679168701172, 3.715679168701172]), var1.eval()) + + def testBasic(self): + self.doTestBasic(use_locking=False) + + def testBasicResource(self): + self.doTestBasic(use_locking=False, use_resource=True) + + def testBasicLocked(self): + self.doTestBasic(use_locking=True) + + def testMinimizeSparseResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable( + [[1.0, 2.0], [3.0, 4.0]], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + loss = pred * pred + sgd_op = adagrad.AdagradOptimizer(1.0).minimize(loss) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType( + [[1.0, 2.0], [3.0, 4.0]], var0.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType( + [[0, 1], [3, 4]], var0.eval(), atol=0.01) + + def testTensorLearningRate(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + ada_opt = adagrad.AdagradOptimizer( + constant_op.constant(3.0), initial_accumulator_value=0.1) + ada_update = ada_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Run 3 steps of adagrad + for _ in range(3): + ada_update.run() + # Validate updated params + self.assertAllCloseAccordingToType( + np.array([-1.6026098728179932, -0.6026098728179932]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([2.715679168701172, 3.715679168701172]), var1.eval()) + + def testSparseBasic(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([[1.0], [2.0]], dtype=dtype) + var1 = variables.Variable([[3.0], [4.0]], dtype=dtype) + grads0 = ops.IndexedSlices( + constant_op.constant( + [0.1], shape=[1, 1], dtype=dtype), + constant_op.constant([0]), + constant_op.constant([2, 1])) + grads1 = ops.IndexedSlices( + constant_op.constant( + [0.01], shape=[1, 1], dtype=dtype), + constant_op.constant([1]), + constant_op.constant([2, 1])) + ada_opt = adagrad.AdagradOptimizer(3.0, initial_accumulator_value=0.1) + ada_update = ada_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([[1.0], [2.0]], var0.eval()) + self.assertAllClose([[3.0], [4.0]], var1.eval()) + # Run 3 step of sgd + for _ in range(3): + ada_update.run() + # Validate updated params + self.assertAllCloseAccordingToType( + np.array([[-1.6026098728179932], [2.0]]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([[3.0], [3.715679168701172]]), var1.eval()) + + def testSparseRepeatedIndices(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + repeated_index_update_var = variables.Variable( + [[1.0], [2.0]], dtype=dtype) + aggregated_update_var = variables.Variable( + [[1.0], [2.0]], dtype=dtype) + grad_repeated_index = ops.IndexedSlices( + constant_op.constant( + [0.1, 0.1], shape=[2, 1], dtype=dtype), + constant_op.constant([1, 1]), + constant_op.constant([2, 1])) + grad_aggregated = ops.IndexedSlices( + constant_op.constant( + [0.2], shape=[1, 1], dtype=dtype), + constant_op.constant([1]), + constant_op.constant([2, 1])) + repeated_update = adagrad.AdagradOptimizer(3.0).apply_gradients( + [(grad_repeated_index, repeated_index_update_var)]) + aggregated_update = adagrad.AdagradOptimizer(3.0).apply_gradients( + [(grad_aggregated, aggregated_update_var)]) + variables.global_variables_initializer().run() + self.assertAllClose(aggregated_update_var.eval(), + repeated_index_update_var.eval()) + for _ in range(3): + repeated_update.run() + aggregated_update.run() + self.assertAllClose(aggregated_update_var.eval(), + repeated_index_update_var.eval()) + + def testSparseRepeatedIndicesResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var_repeated = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtype) + loss_repeated = math_ops.reduce_sum( + embedding_ops.embedding_lookup(var_repeated, [0, 0])) + var_aggregated = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtype) + loss_aggregated = 2 * math_ops.reduce_sum( + embedding_ops.embedding_lookup(var_aggregated, [0])) + update_op_repeated = adagrad.AdagradOptimizer( + 2.0).minimize(loss_repeated) + update_op_aggregated = adagrad.AdagradOptimizer( + 2.0).minimize(loss_aggregated) + variables.global_variables_initializer().run() + self.assertAllCloseAccordingToType( + var_repeated.eval(), var_aggregated.eval()) + for _ in range(3): + update_op_repeated.run() + update_op_aggregated.run() + self.assertAllCloseAccordingToType( + var_repeated.eval(), var_aggregated.eval()) + + def testSparseStability(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + shape = [1, 6] + var0 = variables.Variable( + [[ + 0.00872496, -0.106952, 0.110467, 0.226505, -0.0147257, + -0.0105945 + ]], + dtype=dtype) + grads0 = ops.IndexedSlices( + constant_op.constant( + [[ + -5.91278e-05, 5.31673e-05, -2.5779e-06, 4.29153e-05, + -8.4877e-05, -9.48906e-05 + ]], + shape=shape, + dtype=dtype), + constant_op.constant([0]), + constant_op.constant(shape)) + ada_opt = adagrad.AdagradOptimizer(1.0, initial_accumulator_value=0.1) + ada_update = ada_opt.apply_gradients(zip([grads0], [var0])) + self.assertEqual(["accumulator"], ada_opt.get_slot_names()) + slot0 = ada_opt.get_slot(var0, "accumulator") + init = variables.global_variables_initializer() + for _ in range(100): + init.run() + ada_update.run() + self.assertAllCloseAccordingToType( + np.array([[0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]), slot0.eval()) + self.assertAllCloseAccordingToType( + np.array([[ + 0.00891194, -0.10712013, 0.11047515, 0.22636929, -0.0144573, + -0.01029443 + ]]), var0.eval()) + + def testSharing(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + ada_opt = adagrad.AdagradOptimizer(3.0) + # Apply the optimizer twice. Both applications will use + # the same accums. + ada_update1 = ada_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + ada_update2 = ada_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + self.assertEqual(["accumulator"], ada_opt.get_slot_names()) + slot0 = ada_opt.get_slot(var0, "accumulator") + self.assertEquals(slot0.get_shape(), var0.get_shape()) + slot1 = ada_opt.get_slot(var1, "accumulator") + self.assertEquals(slot1.get_shape(), var1.get_shape()) + variables.global_variables_initializer().run() + + # Fetch params to validate initial values. + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Mix the first and the second adagrad for 3 steps. + ada_update1.run() + ada_update2.run() + ada_update1.run() + # Validate updated params (the same as with only 1 Adagrad). + self.assertAllCloseAccordingToType( + np.array([-1.6026098728179932, -0.6026098728179932]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([2.715679168701172, 3.715679168701172]), var1.eval()) + + def testDynamicShapeVariable_Ok(self): + with self.test_session(): + v = variable_scope.get_variable("v", initializer=constant_op.constant(1.), + validate_shape=False) + self.assertFalse(v.shape.is_fully_defined()) + # Creating optimizer should cause no exception. + adagrad.AdagradOptimizer(3.0, initial_accumulator_value=0.1) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/adam.py b/tensorflow/contrib/optimizer_v2/adam.py new file mode 100644 index 0000000000..42b7f92a76 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adam.py @@ -0,0 +1,202 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Adam optimizer for TensorFlow.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.training import training_ops + + +class AdamOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the Adam algorithm. + + See [Kingma et al., 2014](http://arxiv.org/abs/1412.6980) + ([pdf](http://arxiv.org/pdf/1412.6980.pdf)). + """ + + def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, + use_locking=False, name="Adam"): + """Construct a new Adam optimizer. + + Initialization: + + ``` + m_0 <- 0 (Initialize initial 1st moment vector) + v_0 <- 0 (Initialize initial 2nd moment vector) + t <- 0 (Initialize timestep) + ``` + + The update rule for `variable` with gradient `g` uses an optimization + described at the end of section2 of the paper: + + ``` + t <- t + 1 + lr_t <- learning_rate * sqrt(1 - beta2^t) / (1 - beta1^t) + + m_t <- beta1 * m_{t-1} + (1 - beta1) * g + v_t <- beta2 * v_{t-1} + (1 - beta2) * g * g + variable <- variable - lr_t * m_t / (sqrt(v_t) + epsilon) + ``` + + The default value of 1e-8 for epsilon might not be a good default in + general. For example, when training an Inception network on ImageNet a + current good choice is 1.0 or 0.1. Note that since AdamOptimizer uses the + formulation just before Section 2.1 of the Kingma and Ba paper rather than + the formulation in Algorithm 1, the "epsilon" referred to here is "epsilon + hat" in the paper. + + The sparse implementation of this algorithm (used when the gradient is an + IndexedSlices object, typically because of `tf.gather` or an embedding + lookup in the forward pass) does apply momentum to variable slices even if + they were not used in the forward pass (meaning they have a gradient equal + to zero). Momentum decay (beta1) is also applied to the entire momentum + accumulator. This means that the sparse behavior is equivalent to the dense + behavior (in contrast to some momentum implementations which ignore momentum + unless a variable slice was actually used). + + Some of the args below are hyperparameters where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate. + beta1: A float hyperparameter. The exponential decay rate for the 1st + moment estimates. + beta2: A float hyperparameter. The exponential decay rate for the 2nd + moment estimates. + epsilon: A float hyperparameter. This epsilon is "epsilon hat" in the + Kingma and Ba paper (in the formula just before Section 2.1), not the + epsilon in Algorithm 1 of the paper. + use_locking: If True use locks for update operations. + name: Optional name for the operations created when applying gradients. + Defaults to "Adam". + """ + super(AdamOptimizer, self).__init__(use_locking, name) + + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("beta1", beta1) + self._set_hyper("beta2", beta2) + self._set_hyper("epsilon", epsilon) + + def _get_beta_accumulators(self, state=None): + if state is None: + state = self._get_per_graph_state() + return (state.get_non_slot("beta1_power"), + state.get_non_slot("beta2_power")) + + def _create_vars(self, var_list, state): + # Non-slot variables end up on the same device(s). + state.create_non_slot(initial_value=state.get_hyper("beta1"), + name="beta1_power") + state.create_non_slot(initial_value=state.get_hyper("beta2"), + name="beta2_power") + + # Create slots for the first and second moments. + for v in var_list: + state.zeros_slot(v, "m") + state.zeros_slot(v, "v") + + def _apply_dense(self, grad, var, state): + m = state.get_slot(var, "m") + v = state.get_slot(var, "v") + beta1_power, beta2_power = self._get_beta_accumulators(state) + return training_ops.apply_adam( + var, m, v, + math_ops.cast(beta1_power, var.dtype.base_dtype), + math_ops.cast(beta2_power, var.dtype.base_dtype), + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("beta1", var.dtype.base_dtype), + state.get_hyper("beta2", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, var, state): + m = state.get_slot(var, "m") + v = state.get_slot(var, "v") + beta1_power, beta2_power = self._get_beta_accumulators(state) + return training_ops.resource_apply_adam( + var.handle, m.handle, v.handle, + math_ops.cast(beta1_power, grad.dtype.base_dtype), + math_ops.cast(beta2_power, grad.dtype.base_dtype), + state.get_hyper("learning_rate", grad.dtype.base_dtype), + state.get_hyper("beta1", grad.dtype.base_dtype), + state.get_hyper("beta2", grad.dtype.base_dtype), + state.get_hyper("epsilon", grad.dtype.base_dtype), + grad, use_locking=self._use_locking) + + def _apply_sparse_shared(self, grad, var, indices, scatter_add, state): + beta1_power, beta2_power = self._get_beta_accumulators(state) + beta1_power = math_ops.cast(beta1_power, var.dtype.base_dtype) + beta2_power = math_ops.cast(beta2_power, var.dtype.base_dtype) + lr_t = state.get_hyper("learning_rate", var.dtype.base_dtype) + beta1_t = state.get_hyper("beta1", var.dtype.base_dtype) + beta2_t = state.get_hyper("beta2", var.dtype.base_dtype) + epsilon_t = state.get_hyper("epsilon", var.dtype.base_dtype) + lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power)) + # m_t = beta1 * m + (1 - beta1) * g_t + m = state.get_slot(var, "m") + m_scaled_g_values = grad * (1 - beta1_t) + m_t = state_ops.assign(m, m * beta1_t, + use_locking=self._use_locking) + with ops.control_dependencies([m_t]): + m_t = scatter_add(m, indices, m_scaled_g_values) + # v_t = beta2 * v + (1 - beta2) * (g_t * g_t) + v = state.get_slot(var, "v") + v_scaled_g_values = (grad * grad) * (1 - beta2_t) + v_t = state_ops.assign(v, v * beta2_t, use_locking=self._use_locking) + with ops.control_dependencies([v_t]): + v_t = scatter_add(v, indices, v_scaled_g_values) + v_sqrt = math_ops.sqrt(v_t) + var_update = state_ops.assign_sub(var, + lr * m_t / (v_sqrt + epsilon_t), + use_locking=self._use_locking) + return control_flow_ops.group(*[var_update, m_t, v_t]) + + def _apply_sparse(self, grad, var, state): + return self._apply_sparse_shared( + grad.values, var, grad.indices, + lambda x, i, v: state_ops.scatter_add( # pylint: disable=g-long-lambda + x, i, v, use_locking=self._use_locking), + state) + + def _resource_scatter_add(self, x, i, v): + with ops.control_dependencies( + [resource_variable_ops.resource_scatter_add( + x.handle, i, v)]): + return x.value() + + def _resource_apply_sparse(self, grad, var, indices, state): + return self._apply_sparse_shared( + grad, var, indices, self._resource_scatter_add, state) + + def _finish(self, state): + # Update the power accumulators. + beta1_power, beta2_power = self._get_beta_accumulators(state) + update_beta1 = beta1_power.assign( + beta1_power * state.get_hyper("beta1"), + use_locking=self._use_locking) + update_beta2 = beta2_power.assign( + beta2_power * state.get_hyper("beta2"), + use_locking=self._use_locking) + return control_flow_ops.group(update_beta1, update_beta2) diff --git a/tensorflow/contrib/optimizer_v2/adam_test.py b/tensorflow/contrib/optimizer_v2/adam_test.py new file mode 100644 index 0000000000..d9ad58b0a6 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/adam_test.py @@ -0,0 +1,333 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for Adam optimizer.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.contrib.optimizer_v2 import adam +from tensorflow.python.client import session +from tensorflow.python.eager import context +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +def adam_update_numpy(param, + g_t, + t, + m, + v, + alpha=0.001, + beta1=0.9, + beta2=0.999, + epsilon=1e-8): + alpha_t = alpha * np.sqrt(1 - beta2**t) / (1 - beta1**t) + + m_t = beta1 * m + (1 - beta1) * g_t + v_t = beta2 * v + (1 - beta2) * g_t * g_t + + param_t = param - alpha_t * m_t / (np.sqrt(v_t) + epsilon) + return param_t, m_t, v_t + + +class AdamOptimizerTest(test.TestCase): + + def doTestSparse(self, use_resource=False): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + # Initialize variables for numpy implementation. + m0, v0, m1, v1 = 0.0, 0.0, 0.0, 0.0 + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1, 0.1], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01, 0.01], dtype=dtype.as_numpy_dtype) + + if use_resource: + var0 = resource_variable_ops.ResourceVariable(var0_np) + var1 = resource_variable_ops.ResourceVariable(var1_np) + else: + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0_np_indices = np.array([0, 1], dtype=np.int32) + grads0 = ops.IndexedSlices( + constant_op.constant(grads0_np), + constant_op.constant(grads0_np_indices), constant_op.constant([2])) + grads1_np_indices = np.array([0, 1], dtype=np.int32) + grads1 = ops.IndexedSlices( + constant_op.constant(grads1_np), + constant_op.constant(grads1_np_indices), constant_op.constant([2])) + opt = adam.AdamOptimizer() + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + + beta1_power, beta2_power = opt._get_beta_accumulators() + + # Run 3 steps of Adam + for t in range(1, 4): + self.assertAllCloseAccordingToType(0.9**t, beta1_power.eval()) + self.assertAllCloseAccordingToType(0.999**t, beta2_power.eval()) + update.run() + + var0_np, m0, v0 = adam_update_numpy(var0_np, grads0_np, t, m0, v0) + var1_np, m1, v1 = adam_update_numpy(var1_np, grads1_np, t, m1, v1) + + # Validate updated params + self.assertAllCloseAccordingToType(var0_np, var0.eval()) + self.assertAllCloseAccordingToType(var1_np, var1.eval()) + + def testSparse(self): + self.doTestSparse(use_resource=False) + + def testResourceSparse(self): + self.doTestSparse(use_resource=True) + + def testSparseDevicePlacement(self): + for index_dtype in [dtypes.int32, dtypes.int64]: + with self.test_session(force_gpu=test.is_gpu_available()): + # If a GPU is available, tests that all optimizer ops can be placed on + # it (i.e. they have GPU kernels). + var = variables.Variable([[1.0], [2.0]]) + indices = constant_op.constant([0, 1], dtype=index_dtype) + gathered_sum = math_ops.reduce_sum(array_ops.gather(var, indices)) + optimizer = adam.AdamOptimizer(3.0) + minimize_op = optimizer.minimize(gathered_sum) + variables.global_variables_initializer().run() + minimize_op.run() + + def testSparseRepeatedIndices(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + repeated_index_update_var = variables.Variable( + [[1.0], [2.0]], dtype=dtype) + aggregated_update_var = variables.Variable( + [[1.0], [2.0]], dtype=dtype) + grad_repeated_index = ops.IndexedSlices( + constant_op.constant( + [0.1, 0.1], shape=[2, 1], dtype=dtype), + constant_op.constant([1, 1]), + constant_op.constant([2, 1])) + grad_aggregated = ops.IndexedSlices( + constant_op.constant( + [0.2], shape=[1, 1], dtype=dtype), + constant_op.constant([1]), + constant_op.constant([2, 1])) + repeated_update = adam.AdamOptimizer().apply_gradients( + [(grad_repeated_index, repeated_index_update_var)]) + aggregated_update = adam.AdamOptimizer().apply_gradients( + [(grad_aggregated, aggregated_update_var)]) + variables.global_variables_initializer().run() + self.assertAllClose(aggregated_update_var.eval(), + repeated_index_update_var.eval()) + for _ in range(3): + repeated_update.run() + aggregated_update.run() + self.assertAllClose(aggregated_update_var.eval(), + repeated_index_update_var.eval()) + + def doTestBasic(self, use_resource=False): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + with self.test_session(graph=ops.Graph()): + # Initialize variables for numpy implementation. + m0, v0, m1, v1 = 0.0, 0.0, 0.0, 0.0 + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1, 0.1], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01, 0.01], dtype=dtype.as_numpy_dtype) + + if use_resource: + var0 = resource_variable_ops.ResourceVariable( + var0_np, name="var0_%d" % i) + var1 = resource_variable_ops.ResourceVariable( + var1_np, name="var1_%d" % i) + else: + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0 = constant_op.constant(grads0_np) + grads1 = constant_op.constant(grads1_np) + + opt = adam.AdamOptimizer() + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + opt_variables = opt.variables() + beta1_power, beta2_power = opt._get_beta_accumulators() + self.assertTrue(beta1_power is not None) + self.assertTrue(beta2_power is not None) + self.assertIn(beta1_power, opt_variables) + self.assertIn(beta2_power, opt_variables) + + with ops.Graph().as_default(): + # Shouldn't return non-slot variables from other graphs. + self.assertEqual(0, len(opt.variables())) + + if not context.executing_eagerly(): + self.evaluate(variables.global_variables_initializer()) + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], self.evaluate(var0)) + self.assertAllClose([3.0, 4.0], self.evaluate(var1)) + + beta1_power, beta2_power = opt._get_beta_accumulators() + + # Run 3 steps of Adam + for t in range(1, 4): + if not context.executing_eagerly(): + self.evaluate(update) + elif t > 1: + opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + + self.assertAllCloseAccordingToType(0.9**(t + 1), + self.evaluate(beta1_power)) + self.assertAllCloseAccordingToType(0.999**(t + 1), + self.evaluate(beta2_power)) + + var0_np, m0, v0 = adam_update_numpy(var0_np, grads0_np, t, m0, v0) + var1_np, m1, v1 = adam_update_numpy(var1_np, grads1_np, t, m1, v1) + + # Validate updated params + self.assertAllCloseAccordingToType(var0_np, self.evaluate(var0)) + self.assertAllCloseAccordingToType(var1_np, self.evaluate(var1)) + if use_resource: + self.assertEqual("var0_%d/Adam:0" % (i,), + opt.get_slot(var=var0, name="m").name) + + def testBasic(self): + with self.test_session(): + self.doTestBasic(use_resource=False) + + @test_util.run_in_graph_and_eager_modes(reset_test=True) + def testResourceBasic(self): + self.doTestBasic(use_resource=True) + + def testTensorLearningRate(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + # Initialize variables for numpy implementation. + m0, v0, m1, v1 = 0.0, 0.0, 0.0, 0.0 + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1, 0.1], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01, 0.01], dtype=dtype.as_numpy_dtype) + + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0 = constant_op.constant(grads0_np) + grads1 = constant_op.constant(grads1_np) + opt = adam.AdamOptimizer(constant_op.constant(0.001)) + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + + beta1_power, beta2_power = opt._get_beta_accumulators() + + # Run 3 steps of Adam + for t in range(1, 4): + self.assertAllCloseAccordingToType(0.9**t, beta1_power.eval()) + self.assertAllCloseAccordingToType(0.999**t, beta2_power.eval()) + update.run() + + var0_np, m0, v0 = adam_update_numpy(var0_np, grads0_np, t, m0, v0) + var1_np, m1, v1 = adam_update_numpy(var1_np, grads1_np, t, m1, v1) + + # Validate updated params + self.assertAllCloseAccordingToType(var0_np, var0.eval()) + self.assertAllCloseAccordingToType(var1_np, var1.eval()) + + def testSharing(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + # Initialize variables for numpy implementation. + m0, v0, m1, v1 = 0.0, 0.0, 0.0, 0.0 + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1, 0.1], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01, 0.01], dtype=dtype.as_numpy_dtype) + + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0 = constant_op.constant(grads0_np) + grads1 = constant_op.constant(grads1_np) + opt = adam.AdamOptimizer() + update1 = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + update2 = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + beta1_power, beta2_power = opt._get_beta_accumulators() + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + + # Run 3 steps of intertwined Adam1 and Adam2. + for t in range(1, 4): + self.assertAllCloseAccordingToType(0.9**t, beta1_power.eval()) + self.assertAllCloseAccordingToType(0.999**t, beta2_power.eval()) + if t % 2 == 0: + update1.run() + else: + update2.run() + + var0_np, m0, v0 = adam_update_numpy(var0_np, grads0_np, t, m0, v0) + var1_np, m1, v1 = adam_update_numpy(var1_np, grads1_np, t, m1, v1) + + # Validate updated params + self.assertAllCloseAccordingToType(var0_np, var0.eval()) + self.assertAllCloseAccordingToType(var1_np, var1.eval()) + + def testTwoSessions(self): + optimizer = adam.AdamOptimizer() + g = ops.Graph() + with g.as_default(): + with session.Session(): + var0 = variables.Variable(np.array([1.0, 2.0]), name="v0") + grads0 = constant_op.constant(np.array([0.1, 0.1])) + optimizer.apply_gradients([(grads0, var0)]) + + gg = ops.Graph() + with gg.as_default(): + with session.Session(): + var0 = variables.Variable(np.array([1.0, 2.0]), name="v0") + grads0 = constant_op.constant(np.array([0.1, 0.1])) + + # If the optimizer saves any state not keyed by graph the following line + # fails. + optimizer.apply_gradients([(grads0, var0)]) + + def testSlotsUniqueEager(self): + with context.eager_mode(): + v1 = resource_variable_ops.ResourceVariable(1.) + v2 = resource_variable_ops.ResourceVariable(1.) + opt = adam.AdamOptimizer(1.) + opt.minimize(lambda: v1 + v2) + # There should be two non-slot variables, and two unique slot variables + # for v1 and v2 respectively. + self.assertEqual(6, len(set(opt.variables()))) + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py new file mode 100644 index 0000000000..08f9699e85 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py @@ -0,0 +1,686 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +# TODO(josh11b): Forked from contrib/eager/python to test OptimizerV2 the same way +# OptimizerV1 is tested. This file should be removed once the fork is resolved. + +import functools +import os + +import six + +from tensorflow.contrib.eager.python import checkpointable_utils +from tensorflow.contrib.optimizer_v2 import adam +from tensorflow.python.client import session as session_lib +from tensorflow.python.eager import backprop +from tensorflow.python.eager import context +from tensorflow.python.eager import test +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.keras._impl.keras.engine import training +from tensorflow.python.layers import core +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.training import checkpointable +from tensorflow.python.training import saver as core_saver +from tensorflow.python.training import training_util + + +class NonLayerCheckpointable(checkpointable.Checkpointable): + + def __init__(self): + super(NonLayerCheckpointable, self).__init__() + self.a_variable = checkpointable_utils.add_variable( + self, name="a_variable", shape=[]) + + +# pylint: disable=not-callable +class MyModel(training.Model): + """A concrete Model for testing.""" + + def __init__(self): + super(MyModel, self).__init__() + self._named_dense = core.Dense(1, use_bias=True) + self._second = core.Dense(1, use_bias=False) + # We can still track Checkpointables which aren't Layers. + self._non_layer = NonLayerCheckpointable() + + def call(self, values): + ret = self._second(self._named_dense(values)) + return ret + + +class _MirroringSaveable( + core_saver.BaseSaverBuilder.ResourceVariableSaveable): + + def __init__(self, primary_variable, mirrored_variable, name): + self._primary_variable = primary_variable + self._mirrored_variable = mirrored_variable + super(_MirroringSaveable, self).__init__( + self._primary_variable, "", name) + + def restore(self, restored_tensors, restored_shapes): + """Restore the same value into both variables.""" + tensor, = restored_tensors + return control_flow_ops.group( + self._primary_variable.assign(tensor), + self._mirrored_variable.assign(tensor)) + + +class _OwnsMirroredVariables(checkpointable.CheckpointableBase): + """A Checkpointable object which returns a more complex SaveableObject.""" + + def __init__(self): + self.non_dep_variable = variable_scope.get_variable( + name="non_dep_variable", initializer=6., use_resource=True) + self.mirrored = variable_scope.get_variable( + name="mirrored", initializer=15., use_resource=True) + + def _gather_saveables_for_checkpoint(self): + def _saveable_factory(name=self.non_dep_variable.name): + return _MirroringSaveable( + primary_variable=self.non_dep_variable, + mirrored_variable=self.mirrored, + name=name) + return {checkpointable.VARIABLE_VALUE_KEY: _saveable_factory} + + # The Saver sorts by name before parsing, so we need a name property. + @property + def name(self): + return self.non_dep_variable.name + + +class CheckpointingTests(test.TestCase): + + @test_util.run_in_graph_and_eager_modes(assert_no_eager_garbage=True) + def testNamingWithOptimizer(self): + input_value = constant_op.constant([[3.]]) + model = MyModel() + # A nuisance Model using the same optimizer. Its slot variables should not + # go in the checkpoint, since it is never depended on. + other_model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + optimizer_step = training_util.get_or_create_global_step() + root_checkpointable = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model, optimizer_step=optimizer_step) + if context.executing_eagerly(): + optimizer.minimize( + lambda: model(input_value), + global_step=optimizer_step) + optimizer.minimize( + lambda: other_model(input_value), + global_step=optimizer_step) + else: + train_op = optimizer.minimize( + model(input_value), global_step=optimizer_step) + optimizer.minimize( + other_model(input_value), + global_step=optimizer_step) + self.evaluate(checkpointable_utils.gather_initializers( + root_checkpointable)) + self.evaluate(train_op) + named_variables, serialized_graph = ( + checkpointable_utils._serialize_object_graph(root_checkpointable)) + expected_checkpoint_names = ( + # Created in the root node, so no prefix. + "optimizer_step", + "model/_second/kernel", + "model/_named_dense/kernel", + "model/_named_dense/bias", + # non-Layer dependency of the model + "model/_non_layer/a_variable", + # The optimizer creates two non-slot variables + "optimizer/beta1_power", + "optimizer/beta2_power", + # Slot variables + "model/_second/kernel/.OPTIMIZER_SLOT/optimizer/m", + "model/_second/kernel/.OPTIMIZER_SLOT/optimizer/v", + "model/_named_dense/kernel/.OPTIMIZER_SLOT/optimizer/m", + "model/_named_dense/kernel/.OPTIMIZER_SLOT/optimizer/v", + "model/_named_dense/bias/.OPTIMIZER_SLOT/optimizer/m", + "model/_named_dense/bias/.OPTIMIZER_SLOT/optimizer/v", + ) + suffix = "/.ATTRIBUTES/VARIABLE_VALUE" + expected_checkpoint_names = [ + name + suffix for name in expected_checkpoint_names] + six.assertCountEqual(self, expected_checkpoint_names, + named_variables.keys()) + # Check that we've mapped to the right variable objects (not exhaustive) + self.assertEqual( + "global_step:0", + named_variables["optimizer_step" + suffix].name) + self.assertEqual( + "my_model/dense_1/kernel:0", + named_variables["model/_second/kernel" + suffix].name) + self.assertEqual( + "my_model/dense/kernel:0", + named_variables["model/_named_dense/kernel" + suffix].name) + self.assertEqual( + "beta1_power:0", + named_variables["optimizer/beta1_power" + suffix].name) + self.assertEqual( + "beta2_power:0", + named_variables["optimizer/beta2_power" + suffix].name) + # Spot check the generated protocol buffers. + self.assertEqual("optimizer", + serialized_graph.nodes[0].children[1].local_name) + optimizer_node = serialized_graph.nodes[serialized_graph.nodes[0].children[ + 1].node_id] + self.assertEqual("beta1_power", + optimizer_node.children[0].local_name) + self.assertEqual("beta1_power", + serialized_graph.nodes[optimizer_node.children[0].node_id] + .attributes[0].full_name) + self.assertEqual( + "my_model/dense/kernel", + serialized_graph.nodes[optimizer_node.slot_variables[0] + .original_variable_node_id] + .attributes[0].full_name) + # We strip off the :0 suffix, as variable.name-based saving does. + self.assertEqual( + "my_model/dense/kernel/Adam", + serialized_graph.nodes[optimizer_node.slot_variables[0] + .slot_variable_node_id] + .attributes[0].full_name) + self.assertEqual( + "my_model/dense/kernel/Adam:0", + optimizer.get_slot( + var=named_variables["model/_named_dense/kernel" + suffix], + name="m").name) + self.assertEqual( + "model/_named_dense/kernel" + suffix, + serialized_graph.nodes[ + optimizer_node.slot_variables[0] + .original_variable_node_id].attributes[0].checkpoint_key) + self.assertEqual("m", optimizer_node.slot_variables[0].slot_name) + self.assertEqual( + "model/_named_dense/kernel/.OPTIMIZER_SLOT/optimizer/m" + suffix, + serialized_graph.nodes[ + optimizer_node.slot_variables[0] + .slot_variable_node_id].attributes[0].checkpoint_key) + + @test_util.run_in_graph_and_eager_modes() + def testSaveRestore(self): + model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + root_checkpointable = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model) + input_value = constant_op.constant([[3.]]) + if context.executing_eagerly(): + optimizer.minimize( + lambda: model(input_value)) + else: + train_op = optimizer.minimize(model(input_value)) + # TODO(allenl): Make initialization more pleasant when graph building. + root_checkpointable.save_counter # pylint: disable=pointless-statement + self.evaluate(checkpointable_utils.gather_initializers( + root_checkpointable)) + self.evaluate(train_op) + prefix = os.path.join(self.get_temp_dir(), "ckpt") + self.evaluate(state_ops.assign(model._named_dense.variables[1], [42.])) + m_bias_slot = optimizer.get_slot(model._named_dense.variables[1], "m") + self.evaluate(state_ops.assign(m_bias_slot, [1.5])) + save_path = root_checkpointable.save(file_prefix=prefix) + self.evaluate(state_ops.assign(model._named_dense.variables[1], [43.])) + self.evaluate(state_ops.assign(root_checkpointable.save_counter, 3)) + optimizer_variables = self.evaluate(optimizer.variables()) + self.evaluate(state_ops.assign(m_bias_slot, [-2.])) + # Immediate restoration + status = root_checkpointable.restore(save_path=save_path).assert_consumed() + status.run_restore_ops() + self.assertAllEqual([42.], self.evaluate(model._named_dense.variables[1])) + self.assertAllEqual(1, self.evaluate(root_checkpointable.save_counter)) + self.assertAllEqual([1.5], self.evaluate(m_bias_slot)) + if not context.executing_eagerly(): + return # Restore-on-create is only supported when executing eagerly + on_create_model = MyModel() + on_create_optimizer = adam.AdamOptimizer( + 0.001, + # Preserve beta1_power and beta2_power when appying gradients so we can + # test that they've been restored correctly. + beta1=1.0, beta2=1.0) + on_create_root = checkpointable_utils.Checkpoint( + optimizer=on_create_optimizer, model=on_create_model) + # Deferred restoration + status = on_create_root.restore(save_path=save_path) + on_create_model(constant_op.constant([[3.]])) # create variables + self.assertAllEqual(1, self.evaluate(on_create_root.save_counter)) + self.assertAllEqual([42.], + self.evaluate( + on_create_model._named_dense.variables[1])) + on_create_m_bias_slot = on_create_optimizer.get_slot( + on_create_model._named_dense.variables[1], "m") + # Optimizer slot variables are created when the original variable is + # restored. + self.assertAllEqual([1.5], self.evaluate(on_create_m_bias_slot)) + self.assertAllEqual(optimizer_variables[2:], + self.evaluate(on_create_optimizer.variables())) + dummy_var = resource_variable_ops.ResourceVariable([1.]) + on_create_optimizer.minimize(loss=dummy_var.read_value) + status.assert_consumed() + beta1_power, beta2_power = on_create_optimizer._get_beta_accumulators() + self.assertAllEqual(optimizer_variables[0], self.evaluate(beta1_power)) + self.assertAllEqual(optimizer_variables[1], self.evaluate(beta2_power)) + + # TODO(allenl): Debug garbage created by this test in python3. + def testDeferredRestorationUsageEager(self): + """An idiomatic eager execution example.""" + num_training_steps = 10 + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + for training_continuation in range(3): + model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + root = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model, + optimizer_step=training_util.get_or_create_global_step()) + root.restore(core_saver.latest_checkpoint(checkpoint_directory)) + for _ in range(num_training_steps): + # TODO(allenl): Use a Dataset and serialize/checkpoint it. + input_value = constant_op.constant([[3.]]) + optimizer.minimize( + lambda: model(input_value), # pylint: disable=cell-var-from-loop + global_step=root.optimizer_step) + root.save(file_prefix=checkpoint_prefix) + self.assertEqual((training_continuation + 1) * num_training_steps, + root.optimizer_step.numpy()) + + def testUsageGraph(self): + """Expected usage when graph building.""" + with context.graph_mode(): + num_training_steps = 10 + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + for training_continuation in range(3): + with ops.Graph().as_default(): + model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + root = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model, + global_step=training_util.get_or_create_global_step()) + input_value = constant_op.constant([[3.]]) + train_op = optimizer.minimize( + model(input_value), + global_step=root.global_step) + checkpoint_path = core_saver.latest_checkpoint(checkpoint_directory) + with self.test_session(graph=ops.get_default_graph()) as session: + status = root.restore(save_path=checkpoint_path) + status.initialize_or_restore(session=session) + if checkpoint_path is None: + self.assertEqual(0, training_continuation) + with self.assertRaises(AssertionError): + status.assert_consumed() + else: + status.assert_consumed() + for _ in range(num_training_steps): + session.run(train_op) + root.save(file_prefix=checkpoint_prefix, session=session) + self.assertEqual((training_continuation + 1) * num_training_steps, + session.run(root.global_step)) + self.assertEqual(training_continuation + 1, + session.run(root.save_counter)) + + @test_util.run_in_graph_and_eager_modes() + def testAgnosticUsage(self): + """Graph/eager agnostic usage.""" + # Does create garbage when executing eagerly due to ops.Graph() creation. + num_training_steps = 10 + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + for training_continuation in range(3): + with ops.Graph().as_default(), self.test_session( + graph=ops.get_default_graph()), test_util.device(use_gpu=True): + model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + root = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model, + global_step=training_util.get_or_create_global_step()) + checkpoint_path = core_saver.latest_checkpoint(checkpoint_directory) + status = root.restore(save_path=checkpoint_path) + input_value = constant_op.constant([[3.]]) + train_fn = functools.partial( + optimizer.minimize, + functools.partial(model, input_value), + global_step=root.global_step) + if not context.executing_eagerly(): + train_fn = functools.partial(self.evaluate, train_fn()) + status.initialize_or_restore() + for _ in range(num_training_steps): + train_fn() + root.save(file_prefix=checkpoint_prefix) + self.assertEqual((training_continuation + 1) * num_training_steps, + self.evaluate(root.global_step)) + self.assertEqual(training_continuation + 1, + self.evaluate(root.save_counter)) + + def _get_checkpoint_name(self, name): + root = checkpointable.Checkpointable() + checkpointable_utils.add_variable( + root, name=name, shape=[1, 2], dtype=dtypes.float64) + named_variables, _ = checkpointable_utils._serialize_object_graph(root) + checkpoint_name, = named_variables.keys() + with ops.name_scope("root/" + checkpoint_name): + pass # Make sure we can use this as an op name if we prefix it. + return checkpoint_name + + def testAnonymousVarsInInit(self): + + class Model(training.Model): + + def __init__(self): + super(Model, self).__init__() + self.w = resource_variable_ops.ResourceVariable(0.0) + self.b = resource_variable_ops.ResourceVariable(0.0) + self.vars = [self.w, self.b] + + def call(self, x): + return x * self.w + self.b + + with context.eager_mode(): + model = Model() + optimizer = adam.AdamOptimizer(learning_rate=0.05) + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + checkpoint = checkpointable_utils.Checkpoint( + model=model, optimizer=optimizer) + for _ in range(2): + checkpoint.save(checkpoint_prefix) + with backprop.GradientTape() as tape: + loss = (constant_op.constant(1.) + - model(constant_op.constant(1.))) ** 2 + grad = tape.gradient(loss, model.vars) + optimizer.apply_gradients( + [(g, v) for g, v in zip(grad, model.vars)]) + + @test_util.run_in_graph_and_eager_modes(assert_no_eager_garbage=True) + def testDeferredSlotRestoration(self): + checkpoint_directory = self.get_temp_dir() + + root = checkpointable.Checkpointable() + root.var = checkpointable_utils.add_variable( + root, name="var", initializer=0.) + optimizer = adam.AdamOptimizer(0.1) + if context.executing_eagerly(): + optimizer.minimize(root.var.read_value) + else: + train_op = optimizer.minimize(root.var) + # Note that `optimizer` has not been added as a dependency of + # `root`. Create a one-off grouping so that slot variables for `root.var` + # get initialized too. + self.evaluate(checkpointable_utils.gather_initializers( + checkpointable_utils.Checkpoint(root=root, optimizer=optimizer))) + self.evaluate(train_op) + self.evaluate(state_ops.assign(root.var, 12.)) + no_slots_path = checkpointable_utils.CheckpointableSaver(root).save( + os.path.join(checkpoint_directory, "no_slots")) + root.optimizer = optimizer + self.evaluate(state_ops.assign(root.var, 13.)) + self.evaluate(state_ops.assign(optimizer.get_slot(name="m", var=root.var), + 14.)) + slots_path = checkpointable_utils.CheckpointableSaver(root).save( + os.path.join(checkpoint_directory, "with_slots")) + new_root = checkpointable.Checkpointable() + # Load the slot-containing checkpoint (deferred), then immediately overwrite + # the non-slot variable (also deferred). + slot_status = checkpointable_utils.CheckpointableSaver( + new_root).restore(slots_path) + no_slot_status = checkpointable_utils.CheckpointableSaver( + new_root).restore(no_slots_path) + with self.assertRaises(AssertionError): + no_slot_status.assert_consumed() + new_root.var = checkpointable_utils.add_variable( + new_root, name="var", shape=[]) + no_slot_status.assert_consumed() + no_slot_status.run_restore_ops() + self.assertEqual(12., self.evaluate(new_root.var)) + new_root.optimizer = adam.AdamOptimizer(0.1) + with self.assertRaisesRegexp(AssertionError, "beta1_power"): + slot_status.assert_consumed() + self.assertEqual(12., self.evaluate(new_root.var)) + if context.executing_eagerly(): + # Slot variables are only created with restoring initializers when + # executing eagerly. + self.assertEqual(14., self.evaluate( + new_root.optimizer.get_slot(name="m", var=new_root.var))) + else: + self.assertIs(new_root.optimizer.get_slot(name="m", var=new_root.var), + None) + if context.executing_eagerly(): + new_root.optimizer.minimize(new_root.var.read_value) + else: + train_op = new_root.optimizer.minimize(new_root.var) + # The slot variable now exists; restore() didn't create it, but we should + # now have a restore op for it. + slot_status.run_restore_ops() + self.assertEqual(14., self.evaluate( + new_root.optimizer.get_slot(name="m", var=new_root.var))) + self.evaluate(train_op) + slot_status.assert_consumed() + + def testManySavesGraph(self): + """Saves after the first should not modify the graph.""" + with context.graph_mode(): + graph = ops.Graph() + with graph.as_default(), self.test_session(graph): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + obj = checkpointable.Checkpointable() + obj.var = variable_scope.get_variable(name="v", initializer=0.) + obj.opt = adam.AdamOptimizer(0.1) + obj.opt.minimize(obj.var.read_value()) + self.evaluate(checkpointable_utils.gather_initializers(obj)) + saver = checkpointable_utils.CheckpointableSaver(obj) + saver.save(checkpoint_prefix) + before_ops = graph.get_operations() + saver.save(checkpoint_prefix) + self.assertEqual(before_ops, graph.get_operations()) + + def testManyRestoresGraph(self): + """Restores after the first should not modify the graph.""" + with context.graph_mode(): + graph = ops.Graph() + with graph.as_default(), self.test_session(graph): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + obj = checkpointable.Checkpointable() + obj.var = variable_scope.get_variable(name="v", initializer=0.) + obj.opt = adam.AdamOptimizer(0.1) + obj.opt.minimize(obj.var.read_value()) + self.evaluate(checkpointable_utils.gather_initializers(obj)) + saver = checkpointable_utils.CheckpointableSaver(obj) + save_path = saver.save(checkpoint_prefix) + saver.restore(save_path) + before_ops = graph.get_operations() + saver.restore(save_path) + self.assertEqual(before_ops, graph.get_operations()) + + def testMultipleGraphsNonSlotVariables(self): + with context.graph_mode(): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + optimizer = adam.AdamOptimizer(0.001) + # Construct a model in one graph + first_graph = ops.Graph() + first_session = session_lib.Session(graph=first_graph) + with first_graph.as_default(), first_session.as_default(): + first_variable = resource_variable_ops.ResourceVariable([1.]) + first_root_checkpointable = checkpointable_utils.Checkpoint( + optimizer=optimizer, variable=first_variable) + train_op = optimizer.minimize(first_variable.read_value) + self.evaluate(checkpointable_utils.gather_initializers( + first_root_checkpointable)) + self.evaluate(train_op) + self.evaluate(first_variable.assign([1.])) + self.evaluate(optimizer.get_slot( + var=first_variable, name="m").assign([2.])) + beta1_power, _ = optimizer._get_beta_accumulators() + self.evaluate(beta1_power.assign(3.)) + + # Save and load in a second graph + second_graph = ops.Graph() + with second_graph.as_default(), session_lib.Session(graph=second_graph): + second_variable = resource_variable_ops.ResourceVariable([1.]) + second_root_checkpointable = checkpointable_utils.Checkpoint( + optimizer=optimizer, variable=second_variable) + train_op = optimizer.minimize(second_variable.read_value) + second_root_checkpointable.restore(None).initialize_or_restore() + self.evaluate(train_op) + self.evaluate(second_variable.assign([4.])) + self.evaluate(optimizer.get_slot( + var=second_variable, name="m").assign([5.])) + beta1_power, _ = optimizer._get_beta_accumulators() + self.evaluate(beta1_power.assign(6.)) + save_path = second_root_checkpointable.save(checkpoint_prefix) + self.evaluate(second_variable.assign([7.])) + self.evaluate(optimizer.get_slot( + var=second_variable, name="m").assign([8.])) + beta1_power, _ = optimizer._get_beta_accumulators() + self.assertAllEqual(6., self.evaluate(beta1_power)) + status = second_root_checkpointable.restore(save_path) + status.assert_consumed().run_restore_ops() + self.assertAllEqual([4.], self.evaluate(second_variable)) + self.assertAllEqual([5.], self.evaluate(optimizer.get_slot( + var=second_variable, name="m"))) + beta1_power, _ = optimizer._get_beta_accumulators() + self.assertAllEqual(6., self.evaluate(beta1_power)) + + # Check that the first graph is unmolested + with first_graph.as_default(), first_session.as_default(): + self.assertAllEqual([1.], self.evaluate(first_variable)) + self.assertAllEqual([2.], self.evaluate(optimizer.get_slot( + var=first_variable, name="m"))) + beta1_power, _ = optimizer._get_beta_accumulators() + self.assertAllEqual(3., self.evaluate(beta1_power)) + + +class CheckpointCompatibilityTests(test.TestCase): + + def _initialized_model(self): + input_value = constant_op.constant([[3.]]) + model = MyModel() + optimizer = adam.AdamOptimizer(0.001) + optimizer_step = training_util.get_or_create_global_step() + root_checkpointable = checkpointable_utils.Checkpoint( + optimizer=optimizer, model=model, optimizer_step=optimizer_step) + train_op = optimizer.minimize( + functools.partial(model, input_value), + global_step=optimizer_step) + self.evaluate(checkpointable_utils.gather_initializers( + root_checkpointable)) + self.evaluate(train_op) + # A regular variable, a slot variable, and a non-slot Optimizer variable + # with known values to check when loading. + self.evaluate(model._named_dense.bias.assign([1.])) + self.evaluate(optimizer.get_slot( + var=model._named_dense.bias, name="m").assign([2.])) + beta1_power, _ = optimizer._get_beta_accumulators() + self.evaluate(beta1_power.assign(3.)) + return root_checkpointable + + def _set_sentinels(self, root_checkpointable): + self.evaluate(root_checkpointable.model._named_dense.bias.assign([101.])) + self.evaluate( + root_checkpointable.optimizer.get_slot( + var=root_checkpointable.model._named_dense.bias, name="m") + .assign([102.])) + beta1_power, _ = root_checkpointable.optimizer._get_beta_accumulators() + self.evaluate(beta1_power.assign(103.)) + + def _check_sentinels(self, root_checkpointable): + self.assertAllEqual( + [1.], self.evaluate(root_checkpointable.model._named_dense.bias)) + self.assertAllEqual([2.], self.evaluate( + root_checkpointable.optimizer.get_slot( + var=root_checkpointable.model._named_dense.bias, name="m"))) + beta1_power, _ = root_checkpointable.optimizer._get_beta_accumulators() + self.assertAllEqual(3., self.evaluate(beta1_power)) + + def _write_name_based_checkpoint(self): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + with context.graph_mode(): + save_graph = ops.Graph() + with save_graph.as_default(), self.test_session( + graph=save_graph) as session: + root = self._initialized_model() + name_saver = core_saver.Saver() + return name_saver.save( + sess=session, save_path=checkpoint_prefix, + global_step=root.optimizer_step) + + @test_util.run_in_graph_and_eager_modes() + def testLoadFromNameBasedSaver(self): + """Save a name-based checkpoint, load it using the object-based API.""" + with test_util.device(use_gpu=True): + save_path = self._write_name_based_checkpoint() + root = self._initialized_model() + self._set_sentinels(root) + with self.assertRaises(AssertionError): + self._check_sentinels(root) + object_saver = checkpointable_utils.CheckpointableSaver(root) + status = object_saver.restore(save_path) + with self.assertRaises(AssertionError): + status.assert_consumed() + status.run_restore_ops() + self._check_sentinels(root) + self._set_sentinels(root) + status.initialize_or_restore() + self._check_sentinels(root) + + # TODO(allenl): Test for the core name-based saver loading object-based + # checkpoints once object-based checkpointing is in core. + + def testSaveGraphLoadEager(self): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + with context.graph_mode(): + save_graph = ops.Graph() + with save_graph.as_default(), self.test_session( + graph=save_graph) as session: + root = self._initialized_model() + object_saver = checkpointable_utils.CheckpointableSaver(root) + save_path = object_saver.save( + session=session, file_prefix=checkpoint_prefix) + with context.eager_mode(): + root = self._initialized_model() + self._set_sentinels(root) + root.restore(save_path).assert_consumed() + self._check_sentinels(root) + + def testSaveEagerLoadGraph(self): + checkpoint_directory = self.get_temp_dir() + checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") + with context.eager_mode(): + root = self._initialized_model() + object_saver = checkpointable_utils.CheckpointableSaver(root) + save_path = object_saver.save(file_prefix=checkpoint_prefix) + with context.graph_mode(): + save_graph = ops.Graph() + with save_graph.as_default(), self.test_session( + graph=save_graph): + root = self._initialized_model() + self._set_sentinels(root) + root.restore(save_path).assert_consumed().run_restore_ops() + self._check_sentinels(root) + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/gradient_descent.py b/tensorflow/contrib/optimizer_v2/gradient_descent.py new file mode 100644 index 0000000000..945c8de559 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/gradient_descent.py @@ -0,0 +1,69 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""GradientDescent optimizer for TensorFlow.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.training import training_ops + + +class GradientDescentOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the gradient descent algorithm.""" + + def __init__(self, learning_rate, use_locking=False, name="GradientDescent"): + """Construct a new gradient descent optimizer. + + The learning rate arg below is a hyperparameter where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate to use. + use_locking: If True use locks for update operations. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "GradientDescent". + """ + super(GradientDescentOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + + def _apply_dense(self, grad, var, state): + return training_ops.apply_gradient_descent( + var, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, handle, state): + lr = state.get_hyper("learning_rate", grad.dtype.base_dtype) + return training_ops.resource_apply_gradient_descent( + handle.handle, lr, grad, use_locking=self._use_locking) + + def _resource_apply_sparse_duplicate_indices( + self, grad, handle, indices, state): + lr = state.get_hyper("learning_rate", grad.dtype.base_dtype) + return resource_variable_ops.resource_scatter_add( + handle.handle, indices, -grad * lr) + + def _apply_sparse_duplicate_indices(self, grad, var, state): + delta = ops.IndexedSlices( + grad.values * state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.indices, grad.dense_shape) + return var.scatter_sub(delta, use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/gradient_descent_test.py b/tensorflow/contrib/optimizer_v2/gradient_descent_test.py new file mode 100644 index 0000000000..ad9aef804f --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/gradient_descent_test.py @@ -0,0 +1,223 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Functional test for GradientDescent optimizer.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import gradient_descent +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import embedding_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import resources +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class GradientDescentOptimizerTest(test.TestCase): + + def testBasic(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + optimizer = gradient_descent.GradientDescentOptimizer(3.0) + sgd_op = optimizer.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([1.0, 2.0], var0.eval()) + self.assertAllCloseAccordingToType([3.0, 4.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType([1.0 - 3.0 * 0.1, 2.0 - 3.0 * 0.1], + var0.eval()) + self.assertAllCloseAccordingToType([3.0 - 3.0 * 0.01, 4.0 - 3.0 * 0.01], + var1.eval()) + self.assertEqual(0, len(optimizer.variables())) + + def testBasicResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0).apply_gradients( + zip([grads0, grads1], [var0, var1])) + # TODO(apassos) calling initialize_resources on all resources here + # doesn't work because the sessions and graph are reused across unit + # tests and this would mean trying to reinitialize variables. Figure out + # a long-term solution for this. + resources.initialize_resources([var0, var1]).run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([1.0, 2.0], var0.eval()) + self.assertAllCloseAccordingToType([3.0, 4.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType([1.0 - 3.0 * 0.1, 2.0 - 3.0 * 0.1], + var0.eval()) + self.assertAllCloseAccordingToType([3.0 - 3.0 * 0.01, 4.0 - 3.0 * 0.01], + var1.eval()) + + def testMinimizeResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + var1 = resource_variable_ops.ResourceVariable([3.0], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(var0, x) + var1 + loss = pred * pred + sgd_op = gradient_descent.GradientDescentOptimizer(1.0).minimize(loss) + # TODO(apassos) calling initialize_resources on all resources here + # doesn't work because the sessions and graph are reused across unit + # tests and this would mean trying to reinitialize variables. Figure out + # a long-term solution for this. + resources.initialize_resources([var0, var1]).run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0, 2.0]], var0.eval()) + self.assertAllCloseAccordingToType([3.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + np_pred = 1.0 * 4.0 + 2.0 * 5.0 + 3.0 + np_grad = 2 * np_pred + self.assertAllCloseAccordingToType( + [[1.0 - np_grad * 4.0, 2.0 - np_grad * 5.0]], var0.eval()) + self.assertAllCloseAccordingToType([3.0 - np_grad], var1.eval()) + + def testMinimizeSparseResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + var1 = resource_variable_ops.ResourceVariable([3.0], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + pred += var1 + loss = pred * pred + sgd_op = gradient_descent.GradientDescentOptimizer(1.0).minimize(loss) + # TODO(apassos) calling initialize_resources on all resources here + # doesn't work because the sessions and graph are reused across unit + # tests and this would mean trying to reinitialize variables. Figure out + # a long-term solution for this. + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0, 2.0]], var0.eval()) + self.assertAllCloseAccordingToType([3.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + np_pred = 1.0 * 4.0 + 2.0 * 5.0 + 3.0 + np_grad = 2 * np_pred + self.assertAllCloseAccordingToType( + [[1.0 - np_grad * 4.0, 2.0 - np_grad * 5.0]], var0.eval()) + self.assertAllCloseAccordingToType([3.0 - np_grad], var1.eval()) + + def testTensorLearningRate(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + lrate = constant_op.constant(3.0) + sgd_op = gradient_descent.GradientDescentOptimizer( + lrate).apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([1.0, 2.0], var0.eval()) + self.assertAllCloseAccordingToType([3.0, 4.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType([1.0 - 3.0 * 0.1, 2.0 - 3.0 * 0.1], + var0.eval()) + self.assertAllCloseAccordingToType([3.0 - 3.0 * 0.01, 4.0 - 3.0 * 0.01], + var1.eval()) + + def testGradWrtRef(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + opt = gradient_descent.GradientDescentOptimizer(3.0) + values = [1.0, 3.0] + vars_ = [variables.Variable([v], dtype=dtype) for v in values] + grads_and_vars = opt.compute_gradients(vars_[0] + vars_[1], vars_) + variables.global_variables_initializer().run() + for grad, _ in grads_and_vars: + self.assertAllCloseAccordingToType([1.0], grad.eval()) + + def testWithGlobalStep(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + global_step = variables.Variable(0, trainable=False) + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0).apply_gradients( + zip([grads0, grads1], [var0, var1]), global_step=global_step) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([1.0, 2.0], var0.eval()) + self.assertAllCloseAccordingToType([3.0, 4.0], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params and global_step + self.assertAllCloseAccordingToType([1.0 - 3.0 * 0.1, 2.0 - 3.0 * 0.1], + var0.eval()) + self.assertAllCloseAccordingToType([3.0 - 3.0 * 0.01, 4.0 - 3.0 * 0.01], + var1.eval()) + self.assertAllCloseAccordingToType(1, global_step.eval()) + + def testSparseBasic(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([[1.0], [2.0]], dtype=dtype) + var1 = variables.Variable([[3.0], [4.0]], dtype=dtype) + grads0 = ops.IndexedSlices( + constant_op.constant( + [0.1], shape=[1, 1], dtype=dtype), + constant_op.constant([0]), + constant_op.constant([2, 1])) + grads1 = ops.IndexedSlices( + constant_op.constant( + [0.01], shape=[1, 1], dtype=dtype), + constant_op.constant([1]), + constant_op.constant([2, 1])) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0).apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0], [2.0]], var0.eval()) + self.assertAllCloseAccordingToType([[3.0], [4.0]], var1.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType([[1.0 - 3.0 * 0.1], [2.0]], + var0.eval()) + self.assertAllCloseAccordingToType([[3.0], [4.0 - 3.0 * 0.01]], + var1.eval()) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/momentum.py b/tensorflow/contrib/optimizer_v2/momentum.py new file mode 100644 index 0000000000..0a5aadc2d1 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/momentum.py @@ -0,0 +1,124 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Momentum for TensorFlow.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.training import training_ops + + +class MomentumOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the Momentum algorithm. + + Computes (if `use_nesterov = False`): + + ``` + accumulation = momentum * accumulation + gradient + variable -= learning_rate * accumulation + ``` + + Note that in the dense version of this algorithm, `accumulation` is updated + and applied regardless of a gradient's value, whereas the sparse version (when + the gradient is an `IndexedSlices`, typically because of `tf.gather` or an + embedding) only updates variable slices and corresponding `accumulation` terms + when that part of the variable was used in the forward pass. + """ + + def __init__(self, learning_rate, momentum, + use_locking=False, name="Momentum", use_nesterov=False): + """Construct a new Momentum optimizer. + + Some of the args below are hyperparameters, where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate. + momentum: A float hyperparameter. The momentum. + use_locking: If `True` use locks for update operations. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "Momentum". + use_nesterov: If `True` use Nesterov Momentum. + See [Sutskever et al., 2013]( + http://jmlr.org/proceedings/papers/v28/sutskever13.pdf). + This implementation always computes gradients at the value of the + variable(s) passed to the optimizer. Using Nesterov Momentum makes the + variable(s) track the values called `theta_t + mu*v_t` in the paper. + + @compatibility(eager) + When eager execution is enabled, learning_rate and momentum can each be a + callable that takes no arguments and returns the actual value to use. This + can be useful for changing these values across different invocations of + optimizer functions. + @end_compatibility + """ + super(MomentumOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("momentum", momentum) + self._use_nesterov = use_nesterov + + def _create_vars(self, var_list, state): + for v in var_list: + state.zeros_slot(v, "momentum") + + def _apply_dense(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.apply_momentum( + var, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov).op + + def _resource_apply_dense(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.resource_apply_momentum( + var.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov) + + def _apply_sparse(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.sparse_apply_momentum( + var, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.values, + grad.indices, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov).op + + def _resource_apply_sparse(self, grad, var, indices, state): + mom = state.get_slot(var, "momentum") + return training_ops.resource_sparse_apply_momentum( + var.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + indices, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov) diff --git a/tensorflow/contrib/optimizer_v2/momentum_test.py b/tensorflow/contrib/optimizer_v2/momentum_test.py new file mode 100644 index 0000000000..f37eb48181 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/momentum_test.py @@ -0,0 +1,562 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for Momentum.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from six.moves import xrange # pylint: disable=redefined-builtin + +from tensorflow.contrib.optimizer_v2 import momentum as momentum_lib +from tensorflow.python.eager import context +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import embedding_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class MomentumOptimizerTest(test.TestCase): + + def _update_nesterov_momentum_numpy(self, var, accum, g, lr, momentum): + var = var + accum * lr * momentum + accum = accum * momentum + g + var = var - lr * accum + var = var - accum * lr * momentum + return var, accum + + def doTestBasic(self, use_resource=False, use_callable_params=False): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + if use_resource: + var0 = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtype, name="var0_%d" % i) + var1 = resource_variable_ops.ResourceVariable( + [3.0, 4.0], dtype=dtype, name="var1_%d" % i) + else: + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + learning_rate = lambda: 2.0 + momentum = lambda: 0.9 + if not use_callable_params: + learning_rate = learning_rate() + momentum = momentum() + mom_opt = momentum_lib.MomentumOptimizer( + learning_rate=learning_rate, momentum=momentum) + mom_update = mom_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + + if not context.executing_eagerly(): + self.evaluate(variables.global_variables_initializer()) + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], self.evaluate(var0)) + self.assertAllClose([3.0, 4.0], self.evaluate(var1)) + + # Check we have slots + self.assertEqual(["momentum"], mom_opt.get_slot_names()) + slot0 = mom_opt.get_slot(var0, "momentum") + self.assertEquals(slot0.get_shape(), var0.get_shape()) + slot1 = mom_opt.get_slot(var1, "momentum") + self.assertEquals(slot1.get_shape(), var1.get_shape()) + if not context.executing_eagerly(): + self.assertFalse(slot0 in variables.trainable_variables()) + self.assertFalse(slot1 in variables.trainable_variables()) + + # Step 1: the momentum accumulators where 0. So we should see a normal + # update: v -= grad * learning_rate + if not context.executing_eagerly(): + self.evaluate(mom_update) + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType(np.array([0.1, 0.1]), + self.evaluate(slot0)) + self.assertAllCloseAccordingToType(np.array([0.01, 0.01]), + self.evaluate(slot1)) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([1.0 - (0.1 * 2.0), 2.0 - (0.1 * 2.0)]), + self.evaluate(var0)) + self.assertAllCloseAccordingToType( + np.array([3.0 - (0.01 * 2.0), 4.0 - (0.01 * 2.0)]), + self.evaluate(var1)) + # Step 2: the momentum accumulators contain the previous update. + if context.executing_eagerly(): + mom_opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + else: + self.evaluate(mom_update) + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.1 + 0.1), (0.9 * 0.1 + 0.1)]), + self.evaluate(slot0)) + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.01 + 0.01), (0.9 * 0.01 + 0.01)]), + self.evaluate(slot1)) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0), + 2.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0) + ]), self.evaluate(var0)) + self.assertAllCloseAccordingToType( + np.array([ + 2.98 - ((0.9 * 0.01 + 0.01) * 2.0), 3.98 - ( + (0.9 * 0.01 + 0.01) * 2.0) + ]), self.evaluate(var1)) + + def testBasic(self): + with self.test_session(): + self.doTestBasic(use_resource=False) + + @test_util.run_in_graph_and_eager_modes(reset_test=True) + def testResourceBasic(self): + self.doTestBasic(use_resource=True) + + def testBasicCallableParams(self): + with context.eager_mode(): + self.doTestBasic(use_resource=True, use_callable_params=True) + + @test_util.run_in_graph_and_eager_modes(reset_test=True) + def testVariablesAcrossGraphs(self): + optimizer = momentum_lib.MomentumOptimizer(0.01, 0.5) + with ops.Graph().as_default(): + var0 = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtypes.float32, name="var0") + var1 = resource_variable_ops.ResourceVariable( + [3.0, 4.0], dtype=dtypes.float32, name="var1") + if context.executing_eagerly(): + loss = lambda: math_ops.reduce_sum(var0 + var1) + else: + loss = math_ops.reduce_sum(var0 + var1) + optimizer.minimize(loss) + optimizer_variables = optimizer.variables() + self.assertStartsWith(optimizer_variables[0].name, "var0") + self.assertStartsWith(optimizer_variables[1].name, "var1") + self.assertEquals(2, len(optimizer_variables)) + + with ops.Graph().as_default(): + var2 = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtypes.float32, name="var2") + var3 = resource_variable_ops.ResourceVariable( + [3.0, 4.0], dtype=dtypes.float32, name="var3") + if context.executing_eagerly(): + loss = lambda: math_ops.reduce_sum(var2 + var3) + else: + loss = math_ops.reduce_sum(var2 + var3) + optimizer.minimize(loss) + optimizer_variables = optimizer.variables() + self.assertStartsWith(optimizer_variables[0].name, "var2") + self.assertStartsWith(optimizer_variables[1].name, "var3") + self.assertEquals(2, len(optimizer_variables)) + + def testNesterovMomentum(self): + for dtype in [dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + accum0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + accum1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + cost = 5 * var0 * var0 + 3 * var1 + global_step = variables.Variable( + array_ops.zeros([], dtypes.int64), name="global_step") + mom_op = momentum_lib.MomentumOptimizer( + learning_rate=2.0, momentum=0.9, use_nesterov=True) + opt_op = mom_op.minimize(cost, global_step, [var0, var1]) + variables.global_variables_initializer().run() + for t in range(1, 5): + opt_op.run() + var0_np, accum0_np = self._update_nesterov_momentum_numpy( + var0_np, accum0_np, var0_np * 10, 2.0, 0.9) + var1_np, accum1_np = self._update_nesterov_momentum_numpy(var1_np, + accum1_np, + 3, 2.0, 0.9) + self.assertAllClose(var0_np, var0.eval()) + self.assertAllClose(var1_np, var1.eval()) + + def testSparseNesterovMomentum(self): + for dtype in [dtypes.float32, dtypes.float64]: + with self.test_session(): + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + accum0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + accum1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + grads = [] + for t in range(1, 5): + grads.append(var0_np * 10) + var0_np, accum0_np = self._update_nesterov_momentum_numpy( + var0_np, accum0_np, var0_np * 10, 2.0, 0.9) + var1_np, accum1_np = self._update_nesterov_momentum_numpy(var1_np, + accum1_np, + 3, 2.0, 0.9) + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + accum0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + accum1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + loss = 5 * var0 * var0 + 3 * var1 + mom_op = momentum_lib.MomentumOptimizer( + learning_rate=2.0, momentum=0.9, use_nesterov=True) + x_feed = array_ops.placeholder(dtype) + y_feed = ops.IndexedSlices( + x_feed, constant_op.constant([0, 1]), constant_op.constant([2])) + grads_and_vars = [(y_feed, var0), (constant_op.constant( + [3.0, 3.0], dtype=dtype), var1)] + opt_update = mom_op.apply_gradients(grads_and_vars) + variables.global_variables_initializer().run() + for t in range(1, 5): + opt_update.run(feed_dict={x_feed: grads[t - 1]}) + var0_np, accum0_np = self._update_nesterov_momentum_numpy( + var0_np, accum0_np, var0_np * 10, 2.0, 0.9) + var1_np, accum1_np = self._update_nesterov_momentum_numpy(var1_np, + accum1_np, + 3, 2.0, 0.9) + self.assertAllClose(var0_np, var0.eval()) + self.assertAllClose(var1_np, var1.eval()) + + @test_util.run_in_graph_and_eager_modes(reset_test=True) + def testMinimizeSparseResourceVariable(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + + # pylint: disable=cell-var-from-loop + def loss(): + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + return pred * pred + # pylint: enable=cell-var-from-loop + + opt = momentum_lib.MomentumOptimizer(learning_rate=1.0, momentum=0.0) + sgd_op = opt.minimize(loss) + self.evaluate(variables.global_variables_initializer()) + # Run 1 step of sgd + self.evaluate(sgd_op) + # Validate updated params + self.assertAllCloseAccordingToType([[-111, -138]], self.evaluate(var0)) + + @test_util.run_in_graph_and_eager_modes(reset_test=True) + def testMinimizeWith2DIndiciesForEmbeddingLookup(self): + var0 = resource_variable_ops.ResourceVariable(array_ops.ones([2, 2])) + + def loss(): + return math_ops.reduce_sum(embedding_ops.embedding_lookup(var0, [[1]])) + + opt = momentum_lib.MomentumOptimizer(learning_rate=1.0, momentum=0.0) + sgd_op = opt.minimize(loss) + self.evaluate(variables.global_variables_initializer()) + self.evaluate(sgd_op) + self.assertAllCloseAccordingToType([[1, 1], [0, 0]], self.evaluate(var0)) + + def testTensorLearningRateAndMomentum(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + mom_opt = momentum_lib.MomentumOptimizer( + learning_rate=constant_op.constant(2.0), + momentum=constant_op.constant(0.9)) + mom_update = mom_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + # Check we have slots + self.assertEqual(["momentum"], mom_opt.get_slot_names()) + slot0 = mom_opt.get_slot(var0, "momentum") + self.assertEquals(slot0.get_shape(), var0.get_shape()) + self.assertFalse(slot0 in variables.trainable_variables()) + slot1 = mom_opt.get_slot(var1, "momentum") + self.assertEquals(slot1.get_shape(), var1.get_shape()) + self.assertFalse(slot1 in variables.trainable_variables()) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Step 1: the momentum accumulators where 0. So we should see a normal + # update: v -= grad * learning_rate + mom_update.run() + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType(np.array([0.1, 0.1]), slot0.eval()) + self.assertAllCloseAccordingToType(np.array([0.01, 0.01]), slot1.eval()) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([1.0 - (0.1 * 2.0), 2.0 - (0.1 * 2.0)]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([3.0 - (0.01 * 2.0), 4.0 - (0.01 * 2.0)]), var1.eval()) + # Step 2: the momentum accumulators contain the previous update. + mom_update.run() + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.1 + 0.1), (0.9 * 0.1 + 0.1)]), slot0.eval()) + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.01 + 0.01), (0.9 * 0.01 + 0.01)]), slot1.eval()) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0), + 2.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0) + ]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 2.98 - ((0.9 * 0.01 + 0.01) * 2.0), 3.98 - ( + (0.9 * 0.01 + 0.01) * 2.0) + ]), var1.eval()) + + def _dbParamsMom01(self): + """Return dist-belief momentum values. + + Return values been generated from the dist-belief momentum unittest, + running with a learning rate of 0.1 and a momentum of 0.1. + + These values record how a parameter vector of size 10, initialized with 0.0, + gets updated with 10 consecutive momentum steps. It uses random gradients. + + Returns: + db_grad: The gradients to apply + db_out: The parameters after the momentum update. + """ + db_grad = [[]] * 10 + db_out = [[]] * 10 + # pylint: disable=line-too-long + db_grad[0] = [ + 0.00096264342, 0.17914793, 0.93945462, 0.41396621, 0.53037018, + 0.93197989, 0.78648776, 0.50036013, 0.55345792, 0.96722615 + ] + db_out[0] = [ + -9.6264346e-05, -0.017914793, -0.093945466, -0.041396622, -0.053037018, + -0.093197994, -0.078648776, -0.050036013, -0.055345792, -0.096722618 + ] + db_grad[1] = [ + 0.17075552, 0.88821375, 0.20873757, 0.25236958, 0.57578111, 0.15312378, + 0.5513742, 0.94687688, 0.16012503, 0.22159521 + ] + db_out[1] = [ + -0.017181443, -0.10852765, -0.12421377, -0.070773244, -0.11591884, + -0.11783017, -0.14165108, -0.14972731, -0.076892875, -0.1285544 + ] + db_grad[2] = [ + 0.35077485, 0.47304362, 0.44412705, 0.44368884, 0.078527533, 0.81223965, + 0.31168157, 0.43203235, 0.16792089, 0.24644311 + ] + db_out[2] = [ + -0.053967446, -0.1648933, -0.1716533, -0.1180798, -0.13005978, + -0.20151734, -0.17911947, -0.20289968, -0.095839672, -0.15638189 + ] + db_grad[3] = [ + 0.9694621, 0.75035888, 0.28171822, 0.83813518, 0.53807181, 0.3728098, + 0.81454384, 0.03848977, 0.89759839, 0.93665648 + ] + db_out[3] = [ + -0.15459226, -0.24556576, -0.20456907, -0.20662397, -0.18528105, + -0.24716705, -0.2643207, -0.21206589, -0.18749419, -0.2528303 + ] + db_grad[4] = [ + 0.38578293, 0.8536852, 0.88722926, 0.66276771, 0.13678469, 0.94036359, + 0.69107032, 0.81897682, 0.5433259, 0.67860287 + ] + db_out[4] = [ + -0.20323303, -0.33900154, -0.29658359, -0.28175515, -0.20448165, + -0.34576839, -0.34194785, -0.29488021, -0.25099224, -0.33033544 + ] + db_grad[5] = [ + 0.27885768, 0.76100707, 0.24625534, 0.81354135, 0.18959245, 0.48038563, + 0.84163809, 0.41172323, 0.83259648, 0.44941229 + ] + db_out[5] = [ + -0.23598288, -0.42444581, -0.33041057, -0.3706224, -0.22536094, + -0.40366709, -0.43387437, -0.34433398, -0.34060168, -0.38302717 + ] + db_grad[6] = [ + 0.27233034, 0.056316052, 0.5039115, 0.24105175, 0.35697976, 0.75913221, + 0.73577434, 0.16014607, 0.57500273, 0.071136251 + ] + db_out[6] = [ + -0.26649091, -0.43862185, -0.38418442, -0.40361428, -0.26314685, + -0.48537019, -0.51664448, -0.36529395, -0.40706289, -0.39540997 + ] + db_grad[7] = [ + 0.58697265, 0.2494842, 0.08106143, 0.39954534, 0.15892942, 0.12683646, + 0.74053431, 0.16033, 0.66625422, 0.73515922 + ] + db_out[7] = [ + -0.32823896, -0.46498787, -0.39766794, -0.446868, -0.28281838, + -0.50622416, -0.59897494, -0.38342294, -0.48033443, -0.47016418 + ] + db_grad[8] = [ + 0.8215279, 0.41994119, 0.95172721, 0.68000203, 0.79439718, 0.43384039, + 0.55561525, 0.22567581, 0.93331909, 0.29438227 + ] + db_out[8] = [ + -0.41656655, -0.50961858, -0.49418902, -0.51919359, -0.36422527, + -0.55169362, -0.6627695, -0.40780342, -0.58099347, -0.50707781 + ] + db_grad[9] = [ + 0.68297005, 0.67758518, 0.1748755, 0.13266537, 0.70697063, 0.055731893, + 0.68593478, 0.50580865, 0.12602448, 0.093537711 + ] + db_out[9] = [ + -0.49369633, -0.58184016, -0.52132869, -0.5396927, -0.44306302, + -0.56181377, -0.73774242, -0.46082234, -0.60366184, -0.52012295 + ] + # pylint: enable=line-too-long + return db_grad, db_out + + def testLikeDistBeliefMom01(self): + with self.test_session(): + db_grad, db_out = self._dbParamsMom01() + num_samples = len(db_grad) + var0 = variables.Variable([0.0] * num_samples) + grads0 = constant_op.constant([0.0] * num_samples) + mom_opt = momentum_lib.MomentumOptimizer(learning_rate=0.1, momentum=0.1) + mom_update = mom_opt.apply_gradients(zip([grads0], [var0])) + variables.global_variables_initializer().run() + for i in xrange(num_samples): + mom_update.run(feed_dict={grads0: db_grad[i]}) + self.assertAllClose(np.array(db_out[i]), var0.eval()) + + def testSparse(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable(array_ops.zeros([4, 2], dtype=dtype)) + var1 = variables.Variable(constant_op.constant(1.0, dtype, [4, 2])) + grads0 = ops.IndexedSlices( + constant_op.constant( + [[.1, .1]], dtype=dtype), + constant_op.constant([1]), + constant_op.constant([4, 2])) + grads1 = ops.IndexedSlices( + constant_op.constant( + [[.01, .01], [.01, .01]], dtype=dtype), + constant_op.constant([2, 3]), + constant_op.constant([4, 2])) + mom_opt = momentum_lib.MomentumOptimizer( + learning_rate=2.0, momentum=0.9) + mom_update = mom_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + # Check we have slots + self.assertEqual(["momentum"], mom_opt.get_slot_names()) + slot0 = mom_opt.get_slot(var0, "momentum") + self.assertEquals(slot0.get_shape(), var0.get_shape()) + slot1 = mom_opt.get_slot(var1, "momentum") + self.assertEquals(slot1.get_shape(), var1.get_shape()) + + # Fetch params to validate initial values + self.assertAllClose([0, 0], var0.eval()[0]) + self.assertAllClose([0, 0], var0.eval()[1]) + self.assertAllClose([1, 1], var1.eval()[2]) + + # Step 1: the momentum accumulators are 0. So we should see a normal + # update: v -= grad * learning_rate + mom_update.run() + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType(np.array([0, 0]), slot0.eval()[0]) + self.assertAllCloseAccordingToType(np.array([.1, .1]), slot0.eval()[1]) + self.assertAllCloseAccordingToType( + np.array([.01, .01]), slot1.eval()[2]) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType(np.array([0, 0]), var0.eval()[0]) + self.assertAllCloseAccordingToType( + np.array([-(0.1 * 2.0), -(0.1 * 2.0)]), var0.eval()[1]) + self.assertAllCloseAccordingToType( + np.array([1.0 - (0.01 * 2.0), 1.0 - (0.01 * 2.0)]), var1.eval()[2]) + # Step 2: the momentum accumulators contain the previous update. + mom_update.run() + # Check that the momentum accumulators have been updated. + self.assertAllClose(np.array([0, 0]), slot0.eval()[0]) + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.1 + 0.1), (0.9 * 0.1 + 0.1)]), slot0.eval()[1]) + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.01 + 0.01), (0.9 * 0.01 + 0.01)]), + slot1.eval()[2]) + # Check that the parameters have been updated. + self.assertAllClose(np.array([0, 0]), var0.eval()[0]) + self.assertAllCloseAccordingToType( + np.array([ + -(0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0), -(0.1 * 2.0) - ( + (0.9 * 0.1 + 0.1) * 2.0) + ]), var0.eval()[1]) + self.assertAllCloseAccordingToType( + np.array([ + 0.98 - ((0.9 * 0.01 + 0.01) * 2.0), 0.98 - ( + (0.9 * 0.01 + 0.01) * 2.0) + ]), var1.eval()[2]) + + def testSharing(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + mom_opt = momentum_lib.MomentumOptimizer( + learning_rate=2.0, momentum=0.9) + mom_update1 = mom_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + mom_update2 = mom_opt.apply_gradients( + zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + self.assertEqual(["momentum"], mom_opt.get_slot_names()) + slot0 = mom_opt.get_slot(var0, "momentum") + self.assertEquals(slot0.get_shape(), var0.get_shape()) + slot1 = mom_opt.get_slot(var1, "momentum") + self.assertEquals(slot1.get_shape(), var1.get_shape()) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Step 1: the momentum accumulators where 0. So we should see a normal + # update: v -= grad * learning_rate + mom_update1.run() + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType(np.array([0.1, 0.1]), slot0.eval()) + self.assertAllCloseAccordingToType(np.array([0.01, 0.01]), slot1.eval()) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([1.0 - (0.1 * 2.0), 2.0 - (0.1 * 2.0)]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([3.0 - (0.01 * 2.0), 4.0 - (0.01 * 2.0)]), var1.eval()) + # Step 2: the second momentum accumulators contain the previous update. + mom_update2.run() + # Check that the momentum accumulators have been updated. + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.1 + 0.1), (0.9 * 0.1 + 0.1)]), slot0.eval()) + self.assertAllCloseAccordingToType( + np.array([(0.9 * 0.01 + 0.01), (0.9 * 0.01 + 0.01)]), slot1.eval()) + # Check that the parameters have been updated. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0), + 2.0 - (0.1 * 2.0) - ((0.9 * 0.1 + 0.1) * 2.0) + ]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 2.98 - ((0.9 * 0.01 + 0.01) * 2.0), 3.98 - ( + (0.9 * 0.01 + 0.01) * 2.0) + ]), var1.eval()) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2.py b/tensorflow/contrib/optimizer_v2/optimizer_v2.py new file mode 100644 index 0000000000..471992fdac --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/optimizer_v2.py @@ -0,0 +1,1352 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Version 2 of class Optimizer.""" +# pylint: disable=g-bad-name + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import abc + +from tensorflow.python.eager import backprop +from tensorflow.python.eager import context +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import gradients +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.training import checkpointable +from tensorflow.python.training import distribute as distribute_lib +from tensorflow.python.training import optimizer as optimizer_v1 +from tensorflow.python.training import slot_creator +from tensorflow.python.util import nest + + +class _OptimizableVariable(object): + """Interface for abstracting over variables in the optimizers.""" + + @abc.abstractmethod + def target(self): + """Returns the optimization target for this variable.""" + raise NotImplementedError("Calling an abstract method.") + + @abc.abstractmethod + def update_op(self, optimizer, g, *args): + """Returns the update ops for updating the variable.""" + raise NotImplementedError("Calling an abstract method.") + + +class _RefVariableProcessor(_OptimizableVariable): + """Processor for Variable.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v._ref() # pylint: disable=protected-access + + def update_op(self, optimizer, g, *args): + if isinstance(g, ops.Tensor): + update_op = optimizer._apply_dense(g, self._v, *args) # pylint: disable=protected-access + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + else: + assert isinstance(g, ops.IndexedSlices), ("Gradient ", g, " is neither a " + "tensor nor IndexedSlices.") + if self._v.constraint is not None: + raise RuntimeError( + "Cannot use a constraint function on a sparse variable.") + # pylint: disable=protected-access + return optimizer._apply_sparse_duplicate_indices(g, self._v, *args) + + +class _DenseReadResourceVariableProcessor(_OptimizableVariable): + """Processor for dense ResourceVariables.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + # pylint: disable=protected-access + update_op = optimizer._resource_apply_dense(g, self._v.op.inputs[0], *args) + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + + +class _DenseResourceVariableProcessor(_OptimizableVariable): + """Processor for dense ResourceVariables.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + # pylint: disable=protected-access + if isinstance(g, ops.IndexedSlices): + if self._v.constraint is not None: + raise RuntimeError( + "Cannot use a constraint function on a sparse variable.") + return optimizer._resource_apply_sparse_duplicate_indices( + g.values, self._v, g.indices, *args) + update_op = optimizer._resource_apply_dense(g, self._v, *args) + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + + +class _StreamingModelPortProcessor(_OptimizableVariable): + """Processor for streaming ModelPorts.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + return g + + +class _TensorProcessor(_OptimizableVariable): + """Processor for ordinary Tensors. + + Even though a Tensor can't really be updated, sometimes it is useful to + compute the gradients with respect to a Tensor using the optimizer. Updating + the Tensor is, of course, unsupported. + """ + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + raise NotImplementedError("Trying to update a Tensor ", self._v) + + +def _get_processor(v): + """The processor of v.""" + if context.executing_eagerly(): + if isinstance(v, ops.Tensor): + return _TensorProcessor(v) + else: + return _DenseResourceVariableProcessor(v) + if v.op.type == "VarHandleOp": + return _DenseResourceVariableProcessor(v) + if isinstance(v, variables.Variable): + return _RefVariableProcessor(v) + if v.op.type == "SubmodelPort": + return _StreamingModelPortProcessor(v) + if isinstance(v, ops.Tensor): + return _TensorProcessor(v) + raise NotImplementedError("Trying to optimize unsupported type ", v) + + +def _var_key_v2(var): + """Key for representing a primary variable, for looking up slots.""" + # pylint: disable=protected-access + if hasattr(var, "_mirrored_container"): + mirrored_container = var._mirrored_container() + assert mirrored_container is not None + if context.executing_eagerly(): + return mirrored_container._unique_id + return mirrored_container._shared_name + if context.executing_eagerly(): + return var._unique_id + return var.op.name + + +def _resolve(value, name): + if callable(value): + value = value() + return ops.convert_to_tensor(value, name=name) + + +def _is_dynamic(value): + """Returns true if __init__ arg `value` should be re-evaluated each step.""" + if callable(value): return True + # Don't need to do anything special in graph mode, since dynamic values + # will propagate correctly automatically. + # TODO(josh11b): Add per-device caching across steps using variables for + # truly static values once we add distributed support. + if context.executing_eagerly() and isinstance( + value, resource_variable_ops.ResourceVariable): + return True + return False + + +class _OptimizerV2State(object): + """Holds per-graph and per-step optimizer state. + + Use _init_with_static_hyper() to create the state for a graph, and then + _copy_with_dynamic_hyper() to convert that to state for a particular step. + The difference between the two is that the former only has hyper + parameter values that are static and the latter also has values that + can change every step (according to _is_dynamic()). + """ + + def __init__(self, op_name): + self._op_name = op_name + + def _init_with_static_hyper(self, hyper): + """Initialize a fresh state object from hyper dict.""" + # self._hyper contains a dict from name to a dict with the Tensor values. + # This dict starts with a single item with key "None" with the hyper + # parameter value converted to a Tensor. Other items have dtype keys + # with that Tensor cast to that dtype. + self._hyper = {name: {None: ops.convert_to_tensor(value, name=name)} + for name, (dynamic, value) in hyper.items() if not dynamic} + self._slots = {} + self._non_slot_dict = {} + # Extra state to help Optimizers implement Checkpointable. Holds information + # about variables which will be restored as soon as they're created. + self._deferred_dependencies = {} # Non-slot variables + self._deferred_slot_restorations = {} # Slot variables + + def _copy_with_dynamic_hyper(self, hyper, distribution, non_slot_devices): + """Create a new state object for a particular step.""" + ret = _OptimizerV2State(self._op_name) + # pylint: disable=protected-access + ret._slots = self._slots + ret._non_slot_dict = self._non_slot_dict + ret._deferred_dependencies = self._deferred_dependencies + ret._deferred_slot_restorations = self._deferred_slot_restorations + ret._hyper = {name: {None: _resolve(value, name)} + for name, (dynamic, value) in hyper.items() if dynamic} + ret._hyper.update(self._hyper) + ret._non_slot_devices = non_slot_devices + ret._distribution = distribution + return ret + + def _variables(self): + """Returns a list of all variables held by self.""" + optimizer_variables = list(self._non_slot_dict.values()) + for variable_dict in self._slots.values(): + for slot_for_variable in variable_dict.values(): + optimizer_variables.append(slot_for_variable) + # Sort variables by name so that the return is deterministic. + return sorted(optimizer_variables, key=lambda v: v.name) + + def _slot_dict(self, slot_name): + """Returns a dict for caching slots created under the given name. + + Args: + slot_name: Name for the slot. + + Returns: + A dict that maps primary `Variable` objects to the slot created + for that variable, under the given slot name. + """ + named_slots = self._slots.get(slot_name, None) + if named_slots is None: + named_slots = {} + self._slots[slot_name] = named_slots + return named_slots + + def create_slot(self, var, val, slot_name, optional_op_name=None): + """Find or create a slot for a variable. + + Args: + var: A `Variable` object. + val: A `Tensor`. The initial value of the slot. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that + needs to be created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_slot( + var, val, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, + slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def create_slot_with_initializer(self, var, initializer, shape, dtype, + slot_name, optional_op_name=None): + """Find or create a slot for a variable, using an Initializer. + + Args: + var: A `Variable` object. + initializer: An `Initializer`. The initial value of the slot. + shape: Shape of the initial value of the slot. + dtype: Type of the value of the slot. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that + needs to be created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_slot_with_initializer( + var, initializer, shape, dtype, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, + slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def zeros_slot(self, var, slot_name, optional_op_name=None): + """Find or create a slot initialized with 0.0. + + Args: + var: A `Variable` object. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that + needs to be created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_zeros_slot( + var, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, + slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def _create_or_restore_slot_variable( + self, slot_variable_position, slot_name, variable, + optional_op_name=None): + """Restore a slot variable's value, possibly creating it. + + Called when a variable which has an associated slot variable is created or + restored. When executing eagerly, we create the slot variable with a + restoring initializer. + + No new variables are created when graph building. Instead, + _restore_slot_variable catches these after normal creation and adds restore + ops to the graph. This method is nonetheless important when graph building + for the case when a slot variable has already been created but `variable` + has just been added to a dependency graph (causing us to realize that the + slot variable needs to be restored). + + Args: + slot_variable_position: A `checkpointable._CheckpointPosition` object + indicating the slot variable `Checkpointable` object to be restored. + slot_name: The name of this `Optimizer`'s slot to restore into. + variable: The variable object this slot is being created for. + optional_op_name: Name to use when scoping the Variable that + needs to be created for the slot. + """ + slot_variable = self.get_slot(var=variable, name=slot_name) + if (slot_variable is None and context.executing_eagerly() and + slot_variable_position.is_simple_variable()): + initializer = checkpointable.CheckpointInitialValue( + checkpoint_position=slot_variable_position) + slot_variable = self.create_slot( + var=variable, + val=initializer, + slot_name=slot_name, + optional_op_name=optional_op_name) + # Optimizers do not have unconditional dependencies on their slot + # variables (nor do any other objects). They are only saved if the + # variables they were created for are also saved. + if slot_variable is not None: + # If we've either made this slot variable, or if we've pulled out an + # existing slot variable, we should restore it. + slot_variable_position.restore(slot_variable) + else: + # We didn't make the slot variable. Defer restoring until it gets created + # normally. We keep a list rather than the one with the highest restore + # UID in case slot variables have their own dependencies, in which case + # those could differ between restores. + variable_key = _var_key_v2(variable) + self._deferred_slot_restorations.setdefault( + slot_name, {}).setdefault(variable_key, []).append( + slot_variable_position) + + def get_slot(self, var, name): + """Return a slot named `name` created for `var` by the Optimizer. + + Some `Optimizer` subclasses use additional variables. For example + `Momentum` and `Adagrad` use variables to accumulate updates. This method + gives access to these `Variable` objects if for some reason you need them. + + Use `get_slot_names()` to get the list of slot names created by the + `Optimizer`. + + Args: + var: A variable passed to `minimize()` or `apply_gradients()`. + name: A string. + + Returns: + The `Variable` for the slot if it was created, `None` otherwise. + """ + named_slots = self._slots.get(name, None) + if not named_slots: + return None + return named_slots.get(_var_key_v2(var), None) + + def get_slot_names(self): + """Return a list of the names of slots created by the `Optimizer`. + + See `get_slot()`. + + Returns: + A list of strings. + """ + return sorted(self._slots.keys()) + + def create_non_slot(self, initial_value, name, colocate_with=None): + """Add an extra variable, not associated with a slot.""" + v = self._non_slot_dict.get(name, None) + if v is None: + if colocate_with is None: colocate_with = self._non_slot_devices + with self._distribution.colocate_vars_with(colocate_with): + # TODO(josh11b): Use get_variable() except for the legacy Adam use case. + v = variable_scope.variable(initial_value, name=name, trainable=False) + self._non_slot_dict[name] = v + deferred_dependencies_list = self._deferred_dependencies.pop(name, ()) + for checkpoint_position in sorted( + deferred_dependencies_list, + key=lambda restore: restore.checkpoint.restore_uid, + reverse=True): + checkpoint_position.restore(v) + return v + + def _restore_slot_variable(self, slot_name, variable, slot_variable): + """Restore a newly created slot variable's value.""" + variable_key = _var_key_v2(variable) + deferred_restorations = self._deferred_slot_restorations.get( + slot_name, {}).pop(variable_key, []) + # Iterate over restores, highest restore UID first to minimize the number + # of assignments. + deferred_restorations.sort(key=lambda position: position.restore_uid, + reverse=True) + for checkpoint_position in deferred_restorations: + checkpoint_position.restore(slot_variable) + + def get_non_slot(self, name): + """Returns the non-slot variable identified by `name`.""" + return self._non_slot_dict.get(name, None) + + def get_hyper(self, name, dtype=None): + """Returns the `name` hyper parameter, optionally cast to `dtype`.""" + dtype_dict = self._hyper[name] + # Do we have the value cast to dtype already cached? This should always + # succeed when dtype is None. + if dtype in dtype_dict: + return dtype_dict[dtype] + # Not cached, cast to dtype and save the result in the cache. + result = math_ops.cast(dtype_dict[None], dtype) + dtype_dict[dtype] = result + return result + + +class OptimizerV2(optimizer_v1.Optimizer): + """Updated base class for optimizers. + + This class defines the API to add Ops to train a model. You never use this + class directly, but instead instantiate one of its subclasses such as + `GradientDescentOptimizer`, `AdagradOptimizer`, or `MomentumOptimizer`. + + ### Usage + + ```python + # Create an optimizer with the desired parameters. + opt = GradientDescentOptimizer(learning_rate=0.1) + # Add Ops to the graph to minimize a cost by updating a list of variables. + # "cost" is a Tensor, and the list of variables contains tf.Variable + # objects. + opt_op = opt.minimize(cost, var_list=) + ``` + + In the training program you will just have to run the returned Op. + + ```python + # Execute opt_op to do one step of training: + opt_op.run() + ``` + + ### Processing gradients before applying them. + + Calling `minimize()` takes care of both computing the gradients and + applying them to the variables. If you want to process the gradients + before applying them you can instead use the optimizer in three steps: + + 1. Compute the gradients with `compute_gradients()`. + 2. Process the gradients as you wish. + 3. Apply the processed gradients with `apply_gradients()`. + + Example: + + ```python + # Create an optimizer. + opt = GradientDescentOptimizer(learning_rate=0.1) + + # Compute the gradients for a list of variables. + grads_and_vars = opt.compute_gradients(loss, ) + + # grads_and_vars is a list of tuples (gradient, variable). Do whatever you + # need to the 'gradient' part, for example cap them, etc. + capped_grads_and_vars = [(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars] + + # Ask the optimizer to apply the capped gradients. + opt.apply_gradients(capped_grads_and_vars) + ``` + + ### Gating Gradients + + Both `minimize()` and `compute_gradients()` accept a `gate_gradients` + argument that controls the degree of parallelism during the application of + the gradients. + + The possible values are: `GATE_NONE`, `GATE_OP`, and `GATE_GRAPH`. + + `GATE_NONE`: Compute and apply gradients in parallel. This provides + the maximum parallelism in execution, at the cost of some non-reproducibility + in the results. For example the two gradients of `matmul` depend on the input + values: With `GATE_NONE` one of the gradients could be applied to one of the + inputs _before_ the other gradient is computed resulting in non-reproducible + results. + + `GATE_OP`: For each Op, make sure all gradients are computed before + they are used. This prevents race conditions for Ops that generate gradients + for multiple inputs where the gradients depend on the inputs. + + `GATE_GRAPH`: Make sure all gradients for all variables are computed + before any one of them is used. This provides the least parallelism but can + be useful if you want to process all gradients before applying any of them. + + ### Slots + + Some optimizer subclasses, such as `MomentumOptimizer` and `AdagradOptimizer` + allocate and manage additional variables associated with the variables to + train. These are called Slots. Slots have names and you can ask the + optimizer for the names of the slots that it uses. Once you have a slot name + you can ask the optimizer for the variable it created to hold the slot value. + + This can be useful if you want to log debug a training algorithm, report stats + about the slots, etc. + + ### Non-slot variables + + Some optimizer subclasses, such as `AdamOptimizer` have variables that + are not associated with the variables to train, just the step itself. + + ### Hyper parameters + + These are arguments passed to the optimizer subclass constructor + (the `__init__` method), and then passed to `self._set_hyper()`. + They can be either regular Python values (like 1.0), tensors, or + callables. If they are callable, the callable will be called during + `apply_gradients()` to get the value for the hyper parameter. + + ### State + + Internal methods apre passed a `state` argument with the correct + values to use for the slot and non-slot variables, and the hyper + parameters. + """ + + # Values for gate_gradients. + GATE_NONE = 0 + GATE_OP = 1 + GATE_GRAPH = 2 + + def __init__(self, use_locking, name): + """Create a new Optimizer. + + This must be called by the constructors of subclasses. + Note that Optimizer instances should not bind to a single graph, + and so shouldn't keep Tensors as member variables. Generally + you should be able to use the _set_hyper()/state.get_hyper() + facility instead. + + Args: + use_locking: Bool. If True apply use locks to prevent concurrent updates + to variables. + name: A non-empty string. The name to use for accumulators created + for the optimizer. + + Raises: + ValueError: If name is malformed. + RuntimeError: If _create_slots has been overridden instead of + _create_vars. + """ + # Note: We intentionally don't call parent __init__. + + # Optimizer._create_slots was replaced by _create_vars in OptimizerV2. + if (self.__class__._create_slots.__code__ is not # pylint: disable=protected-access + OptimizerV2._create_slots.__code__): + raise RuntimeError("Override _create_vars instead of _create_slots when " + "descending from OptimizerV2 (class %s)" % + self.__class__.__name__) + if not name: + raise ValueError("Must specify the optimizer name") + + self._use_locking = use_locking + self._name = name + # Map from graph_key to state for that graph. We use the graph_key + # since it works in both eager and graph mode, and gives the outer + # graph inside functions. + tower_context = distribute_lib.get_tower_context() + if tower_context is None: + # In a cross-tower context for a DistributionStrategy, which means + # only one Optimizer will be created, not one per tower. + self._per_graph_state = {} + else: + # We use get_tower_context().merge_call() to get a single dict + # shared across all model replicas when running with a + # DistributionStrategy. + self._per_graph_state = tower_context.merge_call(lambda _: {}) + + # Hyper parameters, and whether they should be re-evaluated every step. + self._hyper = {} + + def _set_hyper(self, name, value): + self._hyper[name] = (_is_dynamic(value), value) + + def minimize(self, loss, global_step=None, var_list=None, + gate_gradients=GATE_OP, aggregation_method=None, + colocate_gradients_with_ops=False, name=None, + grad_loss=None, stop_gradients=None, + scale_loss_by_num_towers=None): + """Add operations to minimize `loss` by updating `var_list`. + + This method simply combines calls `compute_gradients()` and + `apply_gradients()`. If you want to process the gradient before applying + them call `compute_gradients()` and `apply_gradients()` explicitly instead + of using this function. + + Args: + loss: A `Tensor` containing the value to minimize. + global_step: Optional `Variable` to increment by one after the + variables have been updated. + var_list: Optional list or tuple of `Variable` objects to update to + minimize `loss`. Defaults to the list of variables collected in + the graph under the key `GraphKeys.TRAINABLE_VARIABLES`. + gate_gradients: How to gate the computation of gradients. Can be + `GATE_NONE`, `GATE_OP`, or `GATE_GRAPH`. + aggregation_method: Specifies the method used to combine gradient terms. + Valid values are defined in the class `AggregationMethod`. + colocate_gradients_with_ops: If True, try colocating gradients with + the corresponding op. + name: Optional name for the returned operation. + grad_loss: Optional. A `Tensor` holding the gradient computed for `loss`. + stop_gradients: Optional. A Tensor or list of tensors not to differentiate + through. + scale_loss_by_num_towers: Optional boolean. If true, scale the loss + down by the number of towers. By default, auto-detects whether this + is needed. + + Returns: + An Operation that updates the variables in `var_list`. If `global_step` + was not `None`, that operation also increments `global_step`. + + Raises: + ValueError: If some of the variables are not `Variable` objects. + + @compatibility(eager) + When eager execution is enabled, `loss` should be a Python function that + takes elements of `var_list` as arguments and computes the value to be + minimized. If `var_list` is None, `loss` should take no arguments. + Minimization (and gradient computation) is done with respect to the + elements of `var_list` if not None, else with respect to any trainable + variables created during the execution of the `loss` function. + `gate_gradients`, `aggregation_method`, `colocate_gradients_with_ops` and + `grad_loss` are ignored when eager execution is enabled. + @end_compatibility + """ + grads_and_vars = self.compute_gradients( + loss, var_list=var_list, gate_gradients=gate_gradients, + aggregation_method=aggregation_method, + colocate_gradients_with_ops=colocate_gradients_with_ops, + grad_loss=grad_loss, stop_gradients=stop_gradients, + scale_loss_by_num_towers=scale_loss_by_num_towers) + + vars_with_grad = [v for g, v in grads_and_vars if g is not None] + if not vars_with_grad: + raise ValueError( + "No gradients provided for any variable, check your graph for ops" + " that do not support gradients, between variables %s and loss %s." % + ([str(v) for _, v in grads_and_vars], loss)) + + return self.apply_gradients(grads_and_vars, global_step=global_step, + name=name) + + def compute_gradients(self, loss, var_list=None, + gate_gradients=GATE_OP, + aggregation_method=None, + colocate_gradients_with_ops=False, + grad_loss=None, stop_gradients=None, + scale_loss_by_num_towers=None): + """Compute gradients of `loss` for the variables in `var_list`. + + This is the first part of `minimize()`. It returns a list + of (gradient, variable) pairs where "gradient" is the gradient + for "variable". Note that "gradient" can be a `Tensor`, an + `IndexedSlices`, or `None` if there is no gradient for the + given variable. + + Args: + loss: A Tensor containing the value to minimize or a callable taking + no arguments which returns the value to minimize. When eager execution + is enabled it must be a callable. + var_list: Optional list or tuple of `tf.Variable` to update to minimize + `loss`. Defaults to the list of variables collected in the graph + under the key `GraphKeys.TRAINABLE_VARIABLES`. + gate_gradients: How to gate the computation of gradients. Can be + `GATE_NONE`, `GATE_OP`, or `GATE_GRAPH`. + aggregation_method: Specifies the method used to combine gradient terms. + Valid values are defined in the class `AggregationMethod`. + colocate_gradients_with_ops: If True, try colocating gradients with + the corresponding op. + grad_loss: Optional. A `Tensor` holding the gradient computed for `loss`. + stop_gradients: Optional. A Tensor or list of tensors not to differentiate + through. + scale_loss_by_num_towers: Optional boolean. If true, scale the loss + down by the number of towers. By default, auto-detects whether this + is needed. + + Returns: + A list of (gradient, variable) pairs. Variable is always present, but + gradient can be `None`. + + Raises: + TypeError: If `var_list` contains anything else than `Variable` objects. + ValueError: If some arguments are invalid. + RuntimeError: If called with eager execution enabled and `loss` is + not callable. + + @compatibility(eager) + When eager execution is enabled, `gate_gradients`, `aggregation_method`, + and `colocate_gradients_with_ops` are ignored. + @end_compatibility + """ + # TODO(josh11b): Test that we handle weight decay in a reasonable way. + if callable(loss): + with backprop.GradientTape() as tape: + if var_list is not None: + tape.watch(var_list) + loss_value = loss() + + # Scale loss for number of towers (callable-loss case). In this case, + # we have to be careful to call distribute_lib.get_loss_reduction() + # *after* loss() is evaluated, so we know what loss reduction it uses. + if scale_loss_by_num_towers is None: + scale_loss_by_num_towers = ( + distribute_lib.get_loss_reduction() == "mean") + if scale_loss_by_num_towers: + num_towers = distribute_lib.get_distribution_strategy().num_towers + if num_towers > 1: + loss_value *= 1. / num_towers + + if var_list is None: + var_list = tape.watched_variables() + grads = tape.gradient(loss_value, var_list, grad_loss) + return list(zip(grads, var_list)) + if context.executing_eagerly(): + raise RuntimeError( + "`loss` passed to Optimizer.compute_gradients should " + "be a function when eager execution is enabled.") + + # Scale loss for number of towers (non-callable-loss case). + if scale_loss_by_num_towers is None: + scale_loss_by_num_towers = ( + distribute_lib.get_loss_reduction() == "mean") + if scale_loss_by_num_towers: + num_towers = distribute_lib.get_distribution_strategy().num_towers + if num_towers > 1: + loss *= 1. / num_towers + + if gate_gradients not in [optimizer_v1.Optimizer.GATE_NONE, + optimizer_v1.Optimizer.GATE_OP, + optimizer_v1.Optimizer.GATE_GRAPH]: + raise ValueError("gate_gradients must be one of: Optimizer.GATE_NONE, " + "Optimizer.GATE_OP, Optimizer.GATE_GRAPH. Not %s" % + gate_gradients) + self._assert_valid_dtypes([loss]) + if grad_loss is not None: + self._assert_valid_dtypes([grad_loss]) + if var_list is None: + var_list = ( + variables.trainable_variables() + + ops.get_collection(ops.GraphKeys.TRAINABLE_RESOURCE_VARIABLES)) + else: + var_list = nest.flatten(var_list) + # pylint: disable=protected-access + var_list += ops.get_collection(ops.GraphKeys._STREAMING_MODEL_PORTS) + # pylint: enable=protected-access + processors = [_get_processor(v) for v in var_list] + if not var_list: + raise ValueError("No variables to optimize.") + var_refs = [p.target() for p in processors] + grads = gradients.gradients( + loss, var_refs, grad_ys=grad_loss, + gate_gradients=(gate_gradients == optimizer_v1.Optimizer.GATE_OP), + aggregation_method=aggregation_method, + colocate_gradients_with_ops=colocate_gradients_with_ops, + stop_gradients=stop_gradients) + if gate_gradients == optimizer_v1.Optimizer.GATE_GRAPH: + grads = control_flow_ops.tuple(grads) + grads_and_vars = list(zip(grads, var_list)) + self._assert_valid_dtypes( + [v for g, v in grads_and_vars + if g is not None and v.dtype != dtypes.resource]) + return grads_and_vars + + def apply_gradients(self, grads_and_vars, global_step=None, name=None): + """Apply gradients to variables. + + This is the second part of `minimize()`. It returns an `Operation` that + applies gradients. + + Args: + grads_and_vars: List of (gradient, variable) pairs as returned by + `compute_gradients()`. + global_step: Optional `Variable` to increment by one after the + variables have been updated. + name: Optional name for the returned operation. Default to the + name passed to the `Optimizer` constructor. + + Returns: + An `Operation` that applies the specified gradients. If `global_step` + was not None, that operation also increments `global_step`. + + Raises: + TypeError: If `grads_and_vars` is malformed. + ValueError: If none of the variables have gradients. + """ + # This is a default implementation of apply_gradients() that can be shared + # by most optimizers. It relies on the subclass implementing the following + # methods: _create_vars(), _prepare(), _apply_dense(), and _apply_sparse(). + + # Filter out variables with gradients of `None`. + grads_and_vars = tuple(grads_and_vars) # Make sure repeat iteration works. + if not grads_and_vars: + raise ValueError("No variables provided.") + filtered = tuple((g, v) for (g, v) in grads_and_vars if g is not None) + if not filtered: + raise ValueError("No gradients provided for any variable: %s." % + ([str(v) for _, v in grads_and_vars],)) + return distribute_lib.get_tower_context().merge_call( + self.distributed_apply, filtered, global_step=global_step, name=name) + + def _get_or_create_state(self, var_list=None): + """Either looks up or creates `_OptimizerV2State`. + + If any variables are available, they should be passed via the `var_list` + argument, and these will be used to determine the graph to create/retrieve + state for. Otherwise the returned state is for the current default graph. + + Args: + var_list: A list of variables to extract a graph from. + + Returns: + An `_OptimizerV2State` object. + """ + # Determine the graph_key from the current graph. + eager_execution = context.executing_eagerly() + if eager_execution or var_list is None: + graph = ops.get_default_graph() + else: + graph = ops._get_graph_from_inputs(var_list) # pylint: disable=protected-access + assert graph is not None + graph_key = graph._graph_key # pylint: disable=protected-access + + # Get the per graph state by looking up the graph_key. + if graph_key in self._per_graph_state: + per_graph_state = self._per_graph_state[graph_key] + else: + per_graph_state = _OptimizerV2State(self._name) + per_graph_state._init_with_static_hyper(self._hyper) # pylint: disable=protected-access + self._per_graph_state[graph_key] = per_graph_state + return per_graph_state + + def distributed_apply(self, distribution, grads_and_vars, global_step, name): + """`apply_gradients` for use with a `DistributionStrategy`.""" + reduced_grads = distribution.batch_reduce("sum", grads_and_vars) + var_list = [v for _, v in grads_and_vars] + grads_and_vars = zip(reduced_grads, var_list) + + unwrapped_var_list = [x for v in var_list for x in distribution.unwrap(v)] + eager_execution = context.executing_eagerly() + if eager_execution: + # Give a clear error in this case instead of "name not supported + # for Eager Tensors" when we compute non_slot_devices. + for v in unwrapped_var_list: + if isinstance(v, ops.Tensor): + raise NotImplementedError("Trying to update a Tensor ", v) + + with ops.name_scope(name, self._name) as name: + per_graph_state = self._get_or_create_state(var_list=unwrapped_var_list) + # Include the current value of any dynamic hyper parameters in `state`. + non_slot_devices = distribution.non_slot_devices(var_list) + state = per_graph_state._copy_with_dynamic_hyper( # pylint: disable=protected-access + self._hyper, distribution, non_slot_devices) + + # Create any slot and non-slot variables we need in `state`. + with ops.init_scope(): + self._create_vars(var_list, state) + + with ops.name_scope(name): # Re-enter name_scope created above + # Give the child class a chance to do something before we start + # applying gradients. + self._prepare(state) + + def update(v, g): + """Update variable `v` using gradient `g`.""" + assert v is not None + + # Convert the grad to Tensor or IndexedSlices if necessary, and + # look up a processor for each variable's type. + try: + g = ops.convert_to_tensor_or_indexed_slices(g) + except TypeError: + raise TypeError( + "Gradient must be convertible to a Tensor" + " or IndexedSlices, or None: %s" % g) + if not isinstance(g, (ops.Tensor, ops.IndexedSlices)): + raise TypeError( + "Gradient must be a Tensor, IndexedSlices, or None: %s" % g) + processor = _get_processor(v) + + # We colocate all ops created in _apply_dense or _apply_sparse + # on the same device as the variable. + # TODO(apassos): figure out how to get the variable name here. + scope_name = "" if eager_execution else v.op.name + # device_policy is set because non-mirrored tensors will be read in + # `update_op`. + # TODO(josh11b): Make different state objects for each device to + # avoid needing to set the device_policy. + with ops.name_scope("update_" + scope_name), \ + context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + return processor.update_op(self, g, state) + + # Use the processors to update the variables. + update_ops = [] + for grad, var in grads_and_vars: + update_ops.extend(distribution.unwrap(distribution.update( + var, update, grad))) + + # Give the child class a chance to do something after applying + # gradients + def finish(): + # TODO(josh11b): Make different state objects for each device to + # avoid needing to set the device_policy. + with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + return self._finish(state) + + update_ops = control_flow_ops.group(update_ops) + with ops.control_dependencies([update_ops]): + finish_updates = distribution.update_non_slot(non_slot_devices, finish) + if finish_updates is None: + finish_updates = update_ops + + # Update `global_step` (if any). + if global_step is None: + apply_updates = distribution.group(finish_updates, name=name) + else: + with ops.control_dependencies(distribution.unwrap(finish_updates)): + + def update_global_step(global_step): + if isinstance(global_step, resource_variable_ops.ResourceVariable): + return global_step.assign_add( + ops.convert_to_tensor(1, dtype=global_step.dtype), + read_value=False) + else: + return state_ops.assign_add(global_step, 1) + + apply_updates = distribution.group( + distribution.update(global_step, update_global_step), name=name) + + # Add the training op to the TRAIN_OP graph collection in graph mode. + if not eager_execution: + if isinstance(apply_updates, ops.Tensor): + apply_updates = apply_updates.op + train_op = ops.get_collection_ref(ops.GraphKeys.TRAIN_OP) + if apply_updates not in train_op: + train_op.append(apply_updates) + + return apply_updates + + def get_slot(self, var, name): + """Return a slot named `name` created for `var` by the Optimizer. + + Some `Optimizer` subclasses use additional variables. For example + `Momentum` and `Adagrad` use variables to accumulate updates. This method + gives access to these `Variable` objects if for some reason you need them. + + Use `get_slot_names()` to get the list of slot names created by the + `Optimizer`. + + Args: + var: A variable passed to `minimize()` or `apply_gradients()`. + name: A string. + + Returns: + The `Variable` for the slot if it was created, `None` otherwise. + """ + state = self._get_state_for_var(var) + return state.get_slot(var, name) if state is not None else None + + def get_slot_names(self): + """Return a list of the names of slots created by the `Optimizer`. + + See `get_slot()`. + + Returns: + A list of strings. + """ + state = self._get_per_graph_state() + return state.get_slot_names() if state is not None else [] + + def variables(self): + """A list of variables which encode the current state of `Optimizer`. + + Includes slot variables and additional global variables created by the + optimizer in the current default graph. + + Returns: + A list of variables. + """ + state = self._get_per_graph_state() + return state._variables() if state is not None else [] # pylint: disable=protected-access + + # -------------- + # Methods to be implemented by subclasses if they want to use the + # inherited implementation of apply_gradients() or compute_gradients(). + # -------------- + def _create_vars(self, var_list, state): + """Create all slots needed by the variables and any non-slot variables. + + Args: + var_list: A list of `Variable` objects. + state: An object with these methods: + `create_slot(var, val, slot_name, optional_op_name)`, + `create_slot_with_initializer(` + `var, initializer, shape, dtype, slot_name, optional_op_name)`, + `zeros_slot(var, slot_name, optional_op_name)`, + `create_non_slot_variable(initial_value, name, colocate_with)`, + `get_hyper(name)` + """ + # No slots needed by default + pass + + def _prepare(self, state): + """Code to execute before applying gradients. + + Note that most uses of _prepare() in Optimizer have been subsumed + by explicit support for hyper parameters in OptimizerV2 + + Args: + state: An object with a `get_hyper(name)` method. + + Returns: + Return value will be ignored. + """ + pass + + def _apply_dense(self, grad, var, state): + """Add ops to apply dense gradients to `var`. + + Args: + grad: A `Tensor`. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + raise NotImplementedError() + + def _resource_apply_dense(self, grad, handle, state): + """Add ops to apply dense gradients to the variable `handle`. + + Args: + grad: a `Tensor` representing the gradient. + handle: a `Tensor` of dtype `resource` which points to the variable + to be updated. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + raise NotImplementedError() + + def _resource_apply_sparse_duplicate_indices( + self, grad, handle, indices, state): + """Add ops to apply sparse gradients to `handle`, with repeated indices. + + Optimizers which override this method must deal with repeated indices. See + the docstring of `_apply_sparse_duplicate_indices` for details. By default + the correct behavior, to sum non-unique indices and their associated + gradients, is enforced by first pre-processing `grad` and `indices` and + passing them on to `_resource_apply_sparse`. Optimizers which deal correctly + with duplicate indices may instead override this method to avoid the + overhead of summing. + + Args: + grad: a `Tensor` representing the gradient for the affected indices. + handle: a `Tensor` of dtype `resource` which points to the variable + to be updated. + indices: a `Tensor` of integral type representing the indices for + which the gradient is nonzero. Indices may be repeated. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + # pylint: disable=protected-access + summed_grad, unique_indices = optimizer_v1._deduplicate_indexed_slices( + values=grad, indices=indices) + # pylint: enable=protected-access + return self._resource_apply_sparse( + summed_grad, handle, unique_indices, state) + + def _resource_apply_sparse(self, grad, handle, indices, state): + """Add ops to apply sparse gradients to the variable `handle`. + + Similar to `_apply_sparse`, the `indices` argument to this method has been + de-duplicated. Optimizers which deal correctly with non-unique indices may + instead override `_resource_apply_sparse_duplicate_indices` to avoid this + overhead. + + Args: + grad: a `Tensor` representing the gradient for the affected indices. + handle: a `Tensor` of dtype `resource` which points to the variable + to be updated. + indices: a `Tensor` of integral type representing the indices for + which the gradient is nonzero. Indices are unique. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + raise NotImplementedError() + + def _apply_sparse_duplicate_indices(self, grad, var, state): + """Add ops to apply sparse gradients to `var`, with repeated sparse indices. + + Optimizers which override this method must deal with IndexedSlices objects + such as the following: + + IndexedSlicesValue(values=[1, 1], indices=[0, 0], dense_shape=[1]) + + The correct interpretation is: + + IndexedSlicesValue(values=[2], indices=[0], dense_shape=[1]) + + Many optimizers deal incorrectly with repeated indices when updating based + on sparse gradients (e.g. summing squares rather than squaring the sum, or + applying momentum terms multiple times). Adding first is always the correct + behavior, so this is enforced here by reconstructing the IndexedSlices to + have only unique indices, then calling _apply_sparse. + + Optimizers which deal correctly with repeated indices may instead override + this method to avoid the overhead of summing indices. + + Args: + grad: `IndexedSlices`. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + # pylint: disable=protected-access + summed_values, unique_indices = optimizer_v1._deduplicate_indexed_slices( + values=grad.values, indices=grad.indices) + # pylint: enable=protected-access + gradient_no_duplicate_indices = ops.IndexedSlices( + indices=unique_indices, + values=summed_values, + dense_shape=grad.dense_shape) + return self._apply_sparse(gradient_no_duplicate_indices, var, state) + + def _apply_sparse(self, grad, var, state): + """Add ops to apply sparse gradients to `var`. + + The IndexedSlices object passed to `grad` in this function is by default + pre-processed in `_apply_sparse_duplicate_indices` to remove duplicate + indices (see its docstring for details). Optimizers which can tolerate or + have correct special cases for duplicate sparse indices may override + `_apply_sparse_duplicate_indices` instead of this function, avoiding that + overhead. + + Args: + grad: `IndexedSlices`, with no repeated indices. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + raise NotImplementedError() + + def _finish(self, state): + """Do what is needed to finish the update. + + This is called inside a scope colocated with any non-slot variables. + + Args: + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + The operation to apply updates, or None if no updates. + """ + return None + + # -------------- + # Utility methods for subclasses. + # -------------- + def _get_per_graph_state(self): + # pylint: disable=protected-access + return self._per_graph_state.get(ops.get_default_graph()._graph_key, None) + + def _get_state_for_var(self, var): + # pylint: disable=protected-access + return self._per_graph_state.get(var._graph_key, None) + + # -------------- + # Overridden methods from Checkpointable. + # -------------- + + def _track_checkpointable(self, *args, **kwargs): + """Optimizers may not track dependencies. Raises an error.""" + raise NotImplementedError( + "Optimizers may not have dependencies. File a feature request if this " + "limitation bothers you.") + + @property + def _checkpoint_dependencies(self): + """From Checkpointable. Gather graph-specific non-slot variables to save.""" + current_graph_non_slot_variables = [] + state = self._get_per_graph_state() + if state is not None: + for name, variable_object in sorted( + state._non_slot_dict.items(), # pylint: disable=protected-access + # Avoid comparing variables + key=lambda item: item[0]): + current_graph_non_slot_variables.append( + checkpointable.CheckpointableReference( + name=name, ref=variable_object)) + # Note: ignores super(); Optimizers may not have any dependencies outside of + # state objects. + return current_graph_non_slot_variables + + def _lookup_dependency(self, name): + """From Checkpointable. Find a non-slot variable in the current graph.""" + state = self._get_per_graph_state() + if state is None: + return None + else: + return state.get_non_slot(name) + + @property + def _deferred_dependencies(self): + """Lets Checkpointable know where non-slot variables are created. + + If necessary, creates a new state object for the current default graph. + Checkpointable will then add entries to that state's deferred dependency + dictionary. The state object will check that dictionary when creating + non-slot variables, restoring their value if an entry is found. + + Returns: + A dictionary which holds deferred dependencies for the current default + graph. + """ + state = self._get_or_create_state() + return state._deferred_dependencies # pylint: disable=protected-access + + def _create_or_restore_slot_variable( + self, slot_variable_position, slot_name, variable): + """Checkpointable: Restore a slot variable's value, possibly creating it. + + Called when a variable which has an associated slot variable is created or + restored. + + Args: + slot_variable_position: A `checkpointable._CheckpointPosition` object + indicating the slot variable `Checkpointable` object to be restored. + slot_name: The name of this `Optimizer`'s slot to restore into. + variable: The variable object this slot is being created for. + """ + state = self._get_or_create_state(var_list=[variable]) + state._create_or_restore_slot_variable( # pylint: disable=protected-access + slot_variable_position=slot_variable_position, + slot_name=slot_name, + variable=variable, + optional_op_name=self._name) + + # -------------- + # Unsupported parent methods + # -------------- + def _slot_dict(self, slot_name): + raise NotImplementedError( + "_slot_dict() method unsupported in OptimizerV2") + + def _get_or_make_slot(self, var, val, slot_name, op_name): + raise NotImplementedError( + "_get_or_make_slot() method unsupported in OptimizerV2") + + def _get_or_make_slot_with_initializer(self, var, initializer, shape, dtype, + slot_name, op_name): + raise NotImplementedError( + "_get_or_make_slot_with_initializer() method unsupported in " + "OptimizerV2") + + def _create_non_slot_variable(self, initial_value, name, colocate_with): + raise NotImplementedError( + "_create_non_slot_variable() method unsupported in OptimizerV2") + + def _get_non_slot_variable(self, name, graph=None): + raise NotImplementedError( + "_get_non_slot_variable() method unsupported in OptimizerV2") + + def _non_slot_variables(self): + raise NotImplementedError( + "_non_slot_variables() method unsupported in OptimizerV2") diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2_symbols.py b/tensorflow/contrib/optimizer_v2/optimizer_v2_symbols.py new file mode 100644 index 0000000000..24eada06cc --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/optimizer_v2_symbols.py @@ -0,0 +1,42 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Distribution-aware version of Optimizer.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +# pylint: disable=unused-import +from tensorflow.contrib.optimizer_v2.adadelta import AdadeltaOptimizer +from tensorflow.contrib.optimizer_v2.adagrad import AdagradOptimizer +from tensorflow.contrib.optimizer_v2.adam import AdamOptimizer +from tensorflow.contrib.optimizer_v2.gradient_descent import GradientDescentOptimizer +from tensorflow.contrib.optimizer_v2.momentum import MomentumOptimizer +from tensorflow.contrib.optimizer_v2.optimizer_v2 import OptimizerV2 +from tensorflow.contrib.optimizer_v2.rmsprop import RMSPropOptimizer + +from tensorflow.python.util.all_util import remove_undocumented + +_allowed_symbols = [ + 'AdadeltaOptimizer', + 'AdagradOptimizer', + 'AdamOptimizer', + 'GradientDescentOptimizer', + 'MomentumOptimizer', + 'OptimizerV2', + 'RMSPropOptimizer', +] + +remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py b/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py new file mode 100644 index 0000000000..8599af32f6 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py @@ -0,0 +1,294 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Functional test for OptimizerV2.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import gradient_descent +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import clip_ops +from tensorflow.python.ops import gradients_impl +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + + +class OptimizerTest(test.TestCase): + + @test_util.run_in_graph_and_eager_modes() + def testBasic(self): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + # Note that we name the variables uniquely here since the variables don't + # seem to be getting deleted at the end of the loop. + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype, + name='a_%d' % i) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype, + name='b_%d' % i) + def loss(): + return 5 * var0 + 3 * var1 # pylint: disable=cell-var-from-loop + # Note that for eager execution, minimize expects a function instead of a + # Tensor. + global_step = resource_variable_ops.ResourceVariable( + array_ops.zeros([], dtypes.int64), name='global_step_%d' % i) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + + self.evaluate(variables.global_variables_initializer()) + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], self.evaluate(var0)) + self.assertAllClose([3.0, 4.0], self.evaluate(var1)) + # Run 1 step of sgd through optimizer + opt_op = sgd_op.minimize(loss, global_step, [var0, var1]) + self.evaluate(opt_op) + # Validate updated params + self.assertAllClose([-14., -13.], self.evaluate(var0)) + self.assertAllClose([-6., -5.], self.evaluate(var1)) + + def testAggregationMethod(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + cost = 5 * var0 + 3 * var1 + global_step = variables.Variable( + array_ops.zeros([], dtypes.int64), name='global_step') + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + opt_op = sgd_op.minimize( + cost, + global_step, [var0, var1], + aggregation_method=gradients_impl.AggregationMethod. + EXPERIMENTAL_ACCUMULATE_N) + + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Run 1 step of sgd through optimizer + opt_op.run() + # Validate updated params + self.assertAllClose([-14., -13.], var0.eval()) + self.assertAllClose([-6., -5.], var1.eval()) + + def testPrecomputedGradient(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + cost = 5 * var0 + 3 * var1 + grad_loss = constant_op.constant([42, -42], dtype=dtype) + global_step = variables.Variable( + array_ops.zeros([], dtypes.int64), name='global_step') + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + opt_op = sgd_op.minimize( + cost, global_step, [var0, var1], grad_loss=grad_loss) + + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Run 1 step of sgd through optimizer + opt_op.run() + # Validate updated params + self.assertAllClose([1.0 - 3 * 5 * 42.0, 2.0 - 3 * 5 * (-42.0)], + var0.eval()) + self.assertAllClose([3.0 - 3 * 3 * 42.0, 4.0 - 3 * 3 * (-42.0)], + var1.eval()) + + @test_util.run_in_graph_and_eager_modes() + def testNoVariables(self): + for dtype in [dtypes.half, dtypes.float32, dtypes.float64]: + # pylint: disable=cell-var-from-loop + def loss(): + var0 = resource_variable_ops.ResourceVariable( + [1.0, 2.0], dtype=dtype, trainable=False, name='a') + var1 = resource_variable_ops.ResourceVariable( + [3.0, 4.0], dtype=dtype, trainable=False, name='b') + return 5 * var0 + var1 + # pylint: enable=cell-var-from-loop + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + with self.assertRaisesRegexp(ValueError, 'No.*variables'): + sgd_op.minimize(loss) + + @test_util.run_in_graph_and_eager_modes() + def testNoGradients(self): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + # Note that we name the variables uniquely here since the variables don't + # seem to be getting deleted at the end of the loop. + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype, + name='a%d' % i) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype, + name='b%d' % i) + # pylint: disable=cell-var-from-loop + def loss(): + return 5 * var0 + # pylint: enable=cell-var-from-loop + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + with self.assertRaisesRegexp(ValueError, 'No gradients'): + # var1 has no gradient + sgd_op.minimize(loss, var_list=[var1]) + + @test_util.run_in_graph_and_eager_modes() + def testNoGradientsForAnyVariables_Minimize(self): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + # Note that we name the variables uniquely here since the variables don't + # seem to be getting deleted at the end of the loop. + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype, + name='a_%d' % i) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype, + name='b_%d' % i) + def loss(): + return constant_op.constant(5.0) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + with self.assertRaisesRegexp(ValueError, + 'No gradients provided for any variable'): + sgd_op.minimize(loss, var_list=[var0, var1]) + + @test_util.run_in_graph_and_eager_modes() + def testNoGradientsForAnyVariables_ApplyGradients(self): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + # Note that we name the variables uniquely here since the variables don't + # seem to be getting deleted at the end of the loop. + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype, + name='a_%d' % i) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype, + name='b_%d' % i) + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + with self.assertRaisesRegexp(ValueError, + 'No gradients provided for any variable'): + sgd_op.apply_gradients([(None, var0), (None, var1)]) + + @test_util.run_in_graph_and_eager_modes() + def testGradientsAsVariables(self): + for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]): + # Note that we name the variables uniquely here since the variables don't + # seem to be getting deleted at the end of the loop. + var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype, + name='a%d' % i) + var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype, + name='b%d' % i) + def loss(): + return 5 * var0 + 3 * var1 # pylint: disable=cell-var-from-loop + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + grads_and_vars = sgd_op.compute_gradients(loss, [var0, var1]) + # Convert gradients to tf.Variables + converted_grads = [ + resource_variable_ops.ResourceVariable(array_ops.zeros([2], dtype), + name='c_%d_%d' % (i, j)) + for j, gv in enumerate(grads_and_vars) + ] + convert_ops = [ + state_ops.assign(converted_grads[j], gv[0]) + for j, gv in enumerate(grads_and_vars) + ] + + self.evaluate(variables.global_variables_initializer()) + # Run convert_ops to achieve the gradietns converting + self.evaluate(convert_ops) + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], self.evaluate(var0)) + self.assertAllClose([3.0, 4.0], self.evaluate(var1)) + + # Run 1 step of sgd through optimizer + converted_grads_and_vars = list(zip(converted_grads, [var0, var1])) + opt_op = sgd_op.apply_gradients(converted_grads_and_vars) + self.evaluate(opt_op) + + # Validate updated params + self.assertAllClose([-14., -13.], self.evaluate(var0)) + self.assertAllClose([-6., -5.], self.evaluate(var1)) + + @test_util.run_in_graph_and_eager_modes() + def testComputeGradientsWithTensors(self): + x = ops.convert_to_tensor(1.0) + def f(): + return x * x + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + grads_and_vars = sgd_op.compute_gradients(f, [x]) + self.assertEqual(1, len(grads_and_vars)) + grad, x_as_var = grads_and_vars[0] + self.assertIs(x, x_as_var) + self.assertEqual(2.0, self.evaluate(grad)) + + with self.assertRaises(NotImplementedError): + sgd_op.apply_gradients(grads_and_vars) + + def testTrainOp(self): + with self.test_session(): + var0 = variables.Variable([1.0, 2.0]) + var1 = variables.Variable([3.0, 4.0]) + cost = 5 * var0 + 3 * var1 + global_step = variables.Variable( + array_ops.zeros([], dtypes.int64), name='global_step') + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + opt_op = sgd_op.minimize(cost, global_step, [var0, var1]) + self.assertTrue(opt_op in ops.get_collection(ops.GraphKeys.TRAIN_OP)) + + def testConstraint(self): + constraint_01 = lambda x: clip_ops.clip_by_value(x, -0.1, 0.) + constraint_0 = lambda x: clip_ops.clip_by_value(x, 0., 1.) + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], + constraint=constraint_01) + var1 = variables.Variable([3.0, 4.0], + constraint=constraint_0) + cost = 5 * var0 + 3 * var1 + global_step = variables.Variable( + array_ops.zeros([], dtypes.int64), name='global_step') + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + opt_op = sgd_op.minimize(cost, global_step, [var0, var1]) + + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Run 1 step of sgd through optimizer + opt_op.run() + # Validate updated params + self.assertAllClose([-0.1, -0.1], var0.eval()) + self.assertAllClose([0., 0.], var1.eval()) + + def testStopGradients(self): + with self.test_session(): + var0 = variables.Variable([1.0, 2.0], name='var0') + var1 = variables.Variable([3.0, 4.0], name='var1') + var0_id = array_ops.identity(var0) + cost = 5 * var0_id + 3 * var1 + sgd_op = gradient_descent.GradientDescentOptimizer(3.0) + grads_and_vars = sgd_op.compute_gradients(cost, [var0, var1], + stop_gradients=[var0_id]) + grad_dict = {var.op.name: grad for grad, var in grads_and_vars} + self.assertIsNone(grad_dict['var0']) + self.assertIsNotNone(grad_dict['var1']) + + def testDoNotOverrideCreateSlots(self): + class ShouldNotOverrideCreateSlots(optimizer_v2.OptimizerV2): + + def _create_slots(self, var_list): + """In OptimizerV2 _create_slots was renamed _create_vars.""" + return var_list + + with self.assertRaises(RuntimeError): + ShouldNotOverrideCreateSlots(True, 'name') + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/contrib/optimizer_v2/rmsprop.py b/tensorflow/contrib/optimizer_v2/rmsprop.py new file mode 100644 index 0000000000..164ff0ea06 --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/rmsprop.py @@ -0,0 +1,233 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""RMSprop optimizer for Tensorflow. + +rmsprop algorithm [tieleman2012rmsprop] + +A detailed description of rmsprop. + +- maintain a moving (discounted) average of the square of gradients +- divide gradient by the root of this average + +mean_square = decay * mean_square{t-1} + (1-decay) * gradient ** 2 +mom = momentum * mom{t-1} + learning_rate * g_t / sqrt(mean_square + epsilon) +delta = - mom + +This implementation of RMSProp uses plain momentum, not Nesterov momentum. + +The centered version additionally maintains a moving (discounted) average of the +gradients, and uses that average to estimate the variance: + +mean_grad = decay * mean_square{t-1} + (1-decay) * gradient +mean_square = decay * mean_square{t-1} + (1-decay) * gradient ** 2 +mom = momentum * mom{t-1} + learning_rate * g_t / + sqrt(mean_square - mean_grad**2 + epsilon) +delta = - mom +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import init_ops + +from tensorflow.python.training import training_ops + + +class RMSPropOptimizer(optimizer_v2.OptimizerV2): + """Optimizer that implements the RMSProp algorithm. + + See the + [paper](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf). + """ + + def __init__(self, + learning_rate, + decay=0.9, + momentum=0.0, + epsilon=1e-10, + use_locking=False, + centered=False, + name="RMSProp"): + """Construct a new RMSProp optimizer. + + Note that in the dense implementation of this algorithm, variables and their + corresponding accumulators (momentum, gradient moving average, square + gradient moving average) will be updated even if the gradient is zero + (i.e. accumulators will decay, momentum will be applied). The sparse + implementation (used when the gradient is an `IndexedSlices` object, + typically because of `tf.gather` or an embedding lookup in the forward pass) + will not update variable slices or their accumulators unless those slices + were used in the forward pass (nor is there an "eventual" correction to + account for these omitted updates). This leads to more efficient updates for + large embedding lookup tables (where most of the slices are not accessed in + a particular graph execution), but differs from the published algorithm. + + Some of the args below are hyperparameters, where a hyperparameter is + defined as a scalar Tensor, a regular Python value or a callable (which + will be evaluated when `apply_gradients` is called) returning a scalar + Tensor or a Python value. + + Args: + learning_rate: A float hyperparameter. The learning rate. + decay: A float hyperparameter. Discounting factor for the history/coming + gradient. + momentum: A float hyperparameter. + epsilon: A float hyperparameter. Small value to avoid zero denominator. + use_locking: If True use locks for update operation. + centered: If True, gradients are normalized by the estimated variance of + the gradient; if False, by the uncentered second moment. Setting this to + True may help with training, but is slightly more expensive in terms of + computation and memory. Defaults to False. + name: Optional name prefix for the operations created when applying + gradients. Defaults to "RMSProp". + """ + super(RMSPropOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("decay", decay) + self._set_hyper("momentum", momentum) + self._set_hyper("epsilon", epsilon) + + self._centered = centered + + def _create_vars(self, var_list, state): + for v in var_list: + if v.get_shape().is_fully_defined(): + init_rms = init_ops.ones_initializer(dtype=v.dtype.base_dtype) + else: + init_rms = array_ops.ones_like(v) + state.create_slot_with_initializer(v, init_rms, v.get_shape(), + v.dtype.base_dtype, "rms") + if self._centered: + state.zeros_slot(v, "mg") + state.zeros_slot(v, "momentum") + + def _apply_dense(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.apply_centered_rms_prop( + var, + mg, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking).op + else: + return training_ops.apply_rms_prop( + var, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.resource_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + else: + return training_ops.resource_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.sparse_apply_centered_rms_prop( + var, + mg, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + else: + return training_ops.sparse_apply_rms_prop( + var, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = self.get_slot(var, "mg") + return training_ops.resource_sparse_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) + else: + return training_ops.resource_sparse_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/rmsprop_test.py b/tensorflow/contrib/optimizer_v2/rmsprop_test.py new file mode 100644 index 0000000000..ed68f6afbf --- /dev/null +++ b/tensorflow/contrib/optimizer_v2/rmsprop_test.py @@ -0,0 +1,449 @@ +# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for rmsprop optimizer.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import itertools +import math + +import numpy as np + +from tensorflow.contrib.optimizer_v2 import rmsprop +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import embedding_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variables +from tensorflow.python.platform import test + +_DATA_TYPES = [dtypes.half, dtypes.float32] + +_TEST_PARAM_VALUES = [ + # learning_rate, decay, momentum, epsilon, centered, use_resource + [0.5, 0.9, 0.0, 1e-3, True, False], + [0.5, 0.9, 0.0, 1e-3, False, False], + [0.5, 0.9, 0.0, 1e-3, True, True], + [0.5, 0.9, 0.0, 1e-3, False, True], + [0.1, 0.9, 0.0, 1e-3, True, False], + [0.5, 0.95, 0.0, 1e-3, False, False], + [0.5, 0.95, 0.0, 1e-5, True, False], + [0.5, 0.95, 0.9, 1e-5, True, False], +] + +_TESTPARAMS = [ + [data_type] + values + for data_type, values in itertools.product(_DATA_TYPES, _TEST_PARAM_VALUES) +] + + +class RMSPropOptimizerTest(test.TestCase): + + def _rmsprop_update_numpy(self, var, g, mg, rms, mom, lr, decay, momentum, + epsilon, centered): + rms_t = rms * decay + (1 - decay) * g * g + denom_t = rms_t + epsilon + if centered: + mg_t = mg * decay + (1 - decay) * g + denom_t -= mg_t * mg_t + else: + mg_t = mg + mom_t = momentum * mom + lr * g / np.sqrt(denom_t, dtype=denom_t.dtype) + var_t = var - mom_t + return var_t, mg_t, rms_t, mom_t + + def _sparse_rmsprop_update_numpy(self, var, gindexs, gvalues, mg, rms, mom, + lr, decay, momentum, epsilon, centered): + mg_t = copy.deepcopy(mg) + rms_t = copy.deepcopy(rms) + mom_t = copy.deepcopy(mom) + var_t = copy.deepcopy(var) + for i in range(len(gindexs)): + gindex = gindexs[i] + gvalue = gvalues[i] + rms_t[gindex] = rms[gindex] * decay + (1 - decay) * gvalue * gvalue + denom_t = rms_t[gindex] + epsilon + if centered: + mg_t[gindex] = mg_t[gindex] * decay + (1 - decay) * gvalue + denom_t -= mg_t[gindex] * mg_t[gindex] + mom_t[gindex] = momentum * mom[gindex] + lr * gvalue / np.sqrt(denom_t) + var_t[gindex] = var[gindex] - mom_t[gindex] + return var_t, mg_t, rms_t, mom_t + + def testDense(self): + # TODO(yori): Use ParameterizedTest when available + for (dtype, learning_rate, decay, momentum, + epsilon, centered, use_resource) in _TESTPARAMS: + with self.test_session(use_gpu=True): + # Initialize variables for numpy implementation. + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1, 0.2], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01, 0.2], dtype=dtype.as_numpy_dtype) + + if use_resource: + var0 = resource_variable_ops.ResourceVariable(var0_np) + var1 = resource_variable_ops.ResourceVariable(var1_np) + else: + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0 = constant_op.constant(grads0_np) + grads1 = constant_op.constant(grads1_np) + opt = rmsprop.RMSPropOptimizer( + learning_rate=learning_rate, + decay=decay, + momentum=momentum, + epsilon=epsilon, + centered=centered) + + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + mg0 = opt.get_slot(var0, "mg") + self.assertEqual(mg0 is not None, centered) + mg1 = opt.get_slot(var1, "mg") + self.assertEqual(mg1 is not None, centered) + rms0 = opt.get_slot(var0, "rms") + self.assertTrue(rms0 is not None) + rms1 = opt.get_slot(var1, "rms") + self.assertTrue(rms1 is not None) + mom0 = opt.get_slot(var0, "momentum") + self.assertTrue(mom0 is not None) + mom1 = opt.get_slot(var1, "momentum") + self.assertTrue(mom1 is not None) + + mg0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + mg1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + rms0_np = np.array([1.0, 1.0], dtype=dtype.as_numpy_dtype) + rms1_np = np.array([1.0, 1.0], dtype=dtype.as_numpy_dtype) + mom0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + mom1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + + # Run 4 steps of RMSProp + for _ in range(1, 5): + update.run() + + var0_np, mg0_np, rms0_np, mom0_np = self._rmsprop_update_numpy( + var0_np, grads0_np, mg0_np, rms0_np, mom0_np, learning_rate, + decay, momentum, epsilon, centered) + var1_np, mg1_np, rms1_np, mom1_np = self._rmsprop_update_numpy( + var1_np, grads1_np, mg1_np, rms1_np, mom1_np, learning_rate, + decay, momentum, epsilon, centered) + + # Validate updated params + if centered: + self.assertAllCloseAccordingToType(mg0_np, mg0.eval()) + self.assertAllCloseAccordingToType(mg1_np, mg1.eval()) + self.assertAllCloseAccordingToType(rms0_np, rms0.eval()) + self.assertAllCloseAccordingToType(rms1_np, rms1.eval()) + self.assertAllCloseAccordingToType(mom0_np, mom0.eval()) + self.assertAllCloseAccordingToType(mom1_np, mom1.eval()) + self.assertAllCloseAccordingToType(var0_np, var0.eval()) + self.assertAllCloseAccordingToType(var1_np, var1.eval()) + + def testMinimizeSparseResourceVariable(self): + for dtype in [dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + loss = pred * pred + sgd_op = rmsprop.RMSPropOptimizer( + learning_rate=1.0, + decay=0.0, + momentum=0.0, + epsilon=0.0, + centered=False).minimize(loss) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0, 2.0]], var0.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType( + [[0., 1.]], var0.eval(), atol=0.01) + + def testMinimizeSparseResourceVariableCentered(self): + for dtype in [dtypes.float32, dtypes.float64]: + with self.test_session(): + var0 = resource_variable_ops.ResourceVariable([[1.0, 2.0]], dtype=dtype) + x = constant_op.constant([[4.0], [5.0]], dtype=dtype) + pred = math_ops.matmul(embedding_ops.embedding_lookup([var0], [0]), x) + loss = pred * pred + sgd_op = rmsprop.RMSPropOptimizer( + learning_rate=1.0, + decay=0.0, + momentum=0.0, + epsilon=1.0, + centered=True).minimize(loss) + variables.global_variables_initializer().run() + # Fetch params to validate initial values + self.assertAllCloseAccordingToType([[1.0, 2.0]], var0.eval()) + # Run 1 step of sgd + sgd_op.run() + # Validate updated params + self.assertAllCloseAccordingToType( + [[-111, -138]], var0.eval(), atol=0.01) + + def testSparse(self): + # TODO(yori): Use ParameterizedTest when available + for (dtype, learning_rate, decay, + momentum, epsilon, centered, _) in _TESTPARAMS: + with self.test_session(use_gpu=True): + # Initialize variables for numpy implementation. + var0_np = np.array([1.0, 2.0], dtype=dtype.as_numpy_dtype) + grads0_np = np.array([0.1], dtype=dtype.as_numpy_dtype) + var1_np = np.array([3.0, 4.0], dtype=dtype.as_numpy_dtype) + grads1_np = np.array([0.01], dtype=dtype.as_numpy_dtype) + + var0 = variables.Variable(var0_np) + var1 = variables.Variable(var1_np) + grads0_np_indices = np.array([0], dtype=np.int32) + grads0 = ops.IndexedSlices( + constant_op.constant(grads0_np), + constant_op.constant(grads0_np_indices), constant_op.constant([1])) + grads1_np_indices = np.array([1], dtype=np.int32) + grads1 = ops.IndexedSlices( + constant_op.constant(grads1_np), + constant_op.constant(grads1_np_indices), constant_op.constant([1])) + opt = rmsprop.RMSPropOptimizer( + learning_rate=learning_rate, + decay=decay, + momentum=momentum, + epsilon=epsilon, + centered=centered) + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + mg0 = opt.get_slot(var0, "mg") + self.assertEqual(mg0 is not None, centered) + mg1 = opt.get_slot(var1, "mg") + self.assertEqual(mg1 is not None, centered) + rms0 = opt.get_slot(var0, "rms") + self.assertTrue(rms0 is not None) + rms1 = opt.get_slot(var1, "rms") + self.assertTrue(rms1 is not None) + mom0 = opt.get_slot(var0, "momentum") + self.assertTrue(mom0 is not None) + mom1 = opt.get_slot(var1, "momentum") + self.assertTrue(mom1 is not None) + + mg0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + mg1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + rms0_np = np.array([1.0, 1.0], dtype=dtype.as_numpy_dtype) + rms1_np = np.array([1.0, 1.0], dtype=dtype.as_numpy_dtype) + mom0_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + mom1_np = np.array([0.0, 0.0], dtype=dtype.as_numpy_dtype) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + + # Run 4 steps of RMSProp + for _ in range(1, 5): + update.run() + + var0_np, mg0_np, rms0_np, mom0_np = self._sparse_rmsprop_update_numpy( + var0_np, grads0_np_indices, grads0_np, mg0_np, rms0_np, mom0_np, + learning_rate, decay, momentum, epsilon, centered) + var1_np, mg1_np, rms1_np, mom1_np = self._sparse_rmsprop_update_numpy( + var1_np, grads1_np_indices, grads1_np, mg1_np, rms1_np, mom1_np, + learning_rate, decay, momentum, epsilon, centered) + + # Validate updated params + if centered: + self.assertAllCloseAccordingToType(mg0_np, mg0.eval()) + self.assertAllCloseAccordingToType(mg1_np, mg1.eval()) + self.assertAllCloseAccordingToType(rms0_np, rms0.eval()) + self.assertAllCloseAccordingToType(rms1_np, rms1.eval()) + self.assertAllCloseAccordingToType(mom0_np, mom0.eval()) + self.assertAllCloseAccordingToType(mom1_np, mom1.eval()) + self.assertAllCloseAccordingToType(var0_np, var0.eval()) + self.assertAllCloseAccordingToType(var1_np, var1.eval()) + + def testWithoutMomentum(self): + for dtype in [dtypes.half, dtypes.float32]: + with self.test_session(use_gpu=True): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + opt = rmsprop.RMSPropOptimizer( + learning_rate=2.0, decay=0.9, momentum=0.0, epsilon=1.0) + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + rms0 = opt.get_slot(var0, "rms") + self.assertTrue(rms0 is not None) + rms1 = opt.get_slot(var1, "rms") + self.assertTrue(rms1 is not None) + mom0 = opt.get_slot(var0, "momentum") + self.assertTrue(mom0 is not None) + mom1 = opt.get_slot(var1, "momentum") + self.assertTrue(mom1 is not None) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Step 1: the rms accumulators where 1. So we should see a normal + # update: v -= grad * learning_rate + update.run() + # Check the root mean square accumulators. + self.assertAllCloseAccordingToType( + np.array([0.901, 0.901]), rms0.eval()) + self.assertAllCloseAccordingToType( + np.array([0.90001, 0.90001]), rms1.eval()) + # Check the parameters. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1.0)), + 2.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1.0)) + ]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 3.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1.0)), + 4.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1.0)) + ]), var1.eval()) + # Step 2: the root mean square accumulators contain the previous update. + update.run() + # Check the rms accumulators. + self.assertAllCloseAccordingToType( + np.array([0.901 * 0.9 + 0.001, 0.901 * 0.9 + 0.001]), rms0.eval()) + self.assertAllCloseAccordingToType( + np.array([0.90001 * 0.9 + 1e-5, 0.90001 * 0.9 + 1e-5]), rms1.eval()) + # Check the parameters. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1.0)) - + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1.0)), + 2.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1.0)) - + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1.0)) + ]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 3.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1.0)) - + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 1e-5 + 1.0)), + 4.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1.0)) - + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 1e-5 + 1.0)) + ]), var1.eval()) + + def testWithMomentum(self): + for dtype in [dtypes.half, dtypes.float32]: + with self.test_session(use_gpu=True): + var0 = variables.Variable([1.0, 2.0], dtype=dtype) + var1 = variables.Variable([3.0, 4.0], dtype=dtype) + grads0 = constant_op.constant([0.1, 0.1], dtype=dtype) + grads1 = constant_op.constant([0.01, 0.01], dtype=dtype) + + opt = rmsprop.RMSPropOptimizer( + learning_rate=2.0, decay=0.9, momentum=0.5, epsilon=1e-5) + update = opt.apply_gradients(zip([grads0, grads1], [var0, var1])) + variables.global_variables_initializer().run() + + rms0 = opt.get_slot(var0, "rms") + self.assertTrue(rms0 is not None) + rms1 = opt.get_slot(var1, "rms") + self.assertTrue(rms1 is not None) + mom0 = opt.get_slot(var0, "momentum") + self.assertTrue(mom0 is not None) + mom1 = opt.get_slot(var1, "momentum") + self.assertTrue(mom1 is not None) + + # Fetch params to validate initial values + self.assertAllClose([1.0, 2.0], var0.eval()) + self.assertAllClose([3.0, 4.0], var1.eval()) + # Step 1: rms = 1, mom = 0. So we should see a normal + # update: v -= grad * learning_rate + update.run() + # Check the root mean square accumulators. + self.assertAllCloseAccordingToType( + np.array([0.901, 0.901]), rms0.eval()) + self.assertAllCloseAccordingToType( + np.array([0.90001, 0.90001]), rms1.eval()) + # Check the momentum accumulators + self.assertAllCloseAccordingToType( + np.array([(0.1 * 2.0 / math.sqrt(0.901 + 1e-5)), + (0.1 * 2.0 / math.sqrt(0.901 + 1e-5))]), mom0.eval()) + self.assertAllCloseAccordingToType( + np.array([(0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)), + (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5))]), mom1.eval()) + + # Check that the parameters. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)), + 2.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) + ]), var0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 3.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)), + 4.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) + ]), var1.eval()) + + # Step 2: the root mean square accumulators contain the previous update. + update.run() + # Check the rms accumulators. + self.assertAllCloseAccordingToType( + np.array([0.901 * 0.9 + 0.001, 0.901 * 0.9 + 0.001]), rms0.eval()) + self.assertAllCloseAccordingToType( + np.array([0.90001 * 0.9 + 1e-5, 0.90001 * 0.9 + 1e-5]), rms1.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 0.5 * (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) + + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1e-5)), + 0.5 * (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) + + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1e-5)) + ]), mom0.eval()) + self.assertAllCloseAccordingToType( + np.array([ + 0.5 * (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) + + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 2e-5)), + 0.5 * (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) + + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 2e-5)) + ]), mom1.eval()) + + # Check the parameters. + self.assertAllCloseAccordingToType( + np.array([ + 1.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) - + (0.5 * (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) + + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1e-5))), + 2.0 - (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) - + (0.5 * (0.1 * 2.0 / math.sqrt(0.901 + 1e-5)) + + (0.1 * 2.0 / math.sqrt(0.901 * 0.9 + 0.001 + 1e-5))) + ]), var0.eval()) + + self.assertAllCloseAccordingToType( + np.array([ + 3.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) - + (0.5 * (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) + + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 2e-5))), + 4.0 - (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) - + (0.5 * (0.01 * 2.0 / math.sqrt(0.90001 + 1e-5)) + + (0.01 * 2.0 / math.sqrt(0.90001 * 0.9 + 2e-5))) + ]), var1.eval()) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/training/distribute.py b/tensorflow/python/training/distribute.py index d5106752dd..899fda67fe 100644 --- a/tensorflow/python/training/distribute.py +++ b/tensorflow/python/training/distribute.py @@ -99,6 +99,7 @@ _update_device = threading.local() def get_update_device(): + """Get the current device if in a `DistributionStrategy.update()` call.""" try: return _update_device.current except AttributeError: @@ -406,19 +407,19 @@ class DistributionStrategy(object): different across devices, and "Mirrored" when the value are the same. * Unwrapping and merging: Consider calling a function `fn` on multiple devices, like `call_for_each_tower(fn, w)` with an - argument `w that is a wrapped value. This means `w` will have a + argument `w` that is a wrapped value. This means `w` will have a map taking tower device `d0` to `w0`, tower device `d1` to `w1`, etc. `call_for_each_tower()` unwraps `w` before calling `fn`, so it calls `fn(w0)` on `d0`, `fn(w1)` on `d1`, etc. It then merges the return values from `fn()`, which can possibly result in wrapped values. For example, let's say `fn()` returns a tuple with - three components: (x, a, v0) from tower 0, (x, b, v1) on tower 1, + three components: `(x, a, v0)` from tower 0, `(x, b, v1)` on tower 1, etc. If the first component is the same object `x` from every tower, then the first component of the merged result will also be `x`. If the second component is different (`a`, `b`, ...) from each tower, then the merged value will have a wrapped map from tower device to the different values. If the third component is - the members of a mirrored variable (`v` maps `d0` to `v0, `d1` to + the members of a mirrored variable (`v` maps `d0` to `v0`, `d1` to `v1`, etc.), then the merged result will be that mirrored variable (`v`). * Tower context vs. Cross-tower context: _tower context_ is when we diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index d22a465376..34dd419f15 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -211,7 +211,6 @@ def _get_default_do_not_descend_map(): 'tf': ['cli', 'lib', 'wrappers'], 'tf.contrib': [ 'compiler', - 'distribute', 'grid_rnn', # Block contrib.keras to de-clutter the docs 'keras', -- GitLab From 79a5ae8ccf1af9e46e10a1e9f8347b33343b06e8 Mon Sep 17 00:00:00 2001 From: Priya Gupta Date: Thu, 29 Mar 2018 15:32:14 -0700 Subject: [PATCH 445/906] Internal Change PiperOrigin-RevId: 190996815 --- tensorflow/contrib/distribute/README.md | 143 ++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tensorflow/contrib/distribute/README.md diff --git a/tensorflow/contrib/distribute/README.md b/tensorflow/contrib/distribute/README.md new file mode 100644 index 0000000000..ba9a392c77 --- /dev/null +++ b/tensorflow/contrib/distribute/README.md @@ -0,0 +1,143 @@ +# Distribution Strategy + +> *NOTE*: This is a experimental feature. The API and performance +> characteristics are subject to change. + +## Overview + +[`DistributionStrategy`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/distribute/DistributionStrategy) +API is an easy way to distribute your training +across multiple devices/machines. Our goal is to allow users to use existing +models and training code with minimal changes to enable distributed training. +Moreover, we've design the API in such a way that it works with both eager and +graph execution. + +Currently we support one type of strategy, called +[`MirroredStrategy`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/distribute/MirroredStrategy). +It does in-graph replication with synchronous training +on many GPUs on one machine. Essentially, we create copies of all variables in +the model's layers on each device. We then use all-reduce to combine gradients +across the devices before applying them to the variables to keep them in sync. +In the future, we intend to support other kinds of training configurations such +as multi-node, synchronous, +[asynchronous](https://www.tensorflow.org/deploy/distributed#putting_it_all_together_example_trainer_program), +parameter servers and model parallelism. + +## Example + +Let's demonstrate how to use this API with a simple example. We will use the +[`Estimator`](https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator) +approach, and show you how to scale your model to run on multiple GPUs on one +machine using `MirroredStrategy`. + +Let's consider a very simple model function which tries to learn a simple +function. + +```python +def model_fn(features, labels, mode): + layer = tf.layers.Dense(1) + logits = layer(features) + + if mode == tf.estimator.ModeKeys.PREDICT: + predictions = {"logits": logits} + return tf.estimator.EstimatorSpec(mode, predictions=predictions) + + loss = tf.losses.mean_squared_error( + labels=labels, predictions=tf.reshape(logits, [])) + + if mode == tf.estimator.ModeKeys.EVAL: + return tf.estimator.EstimatorSpec(mode, loss=loss) + + if mode == tf.estimator.ModeKeys.TRAIN: + train_op = tf.train.GradientDescentOptimizer(0.2).minimize(loss_fn()) + return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op) +``` + +Let's also define a simple input function to feed data for training this model. +Note that we require using +[`tf.data.Dataset`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) +with `DistributionStrategy`. + + +```python +def input_fn(): + features = tf.data.Dataset.from_tensors([[1.]]).repeat(100) + labels = tf.data.Dataset.from_tensors(1.).repeat(100) + return dataset_ops.Dataset.zip((features, labels)) +``` + +Now that we have a model function and input function defined, we can define the +estimator. To use `MirroredStrategy`, all we need to do is: + +* Create an instance of the `MirroredStrategy` class. +* Pass it to the +[`RunConfig`](https://www.tensorflow.org/api_docs/python/tf/estimator/RunConfig) +parameter of `Estimator`. + + +```python +distribution = tf.contrib.distribute.MirroredStrategy() +config = tf.estimator.RunConfig(distribute=distribution) +classifier = tf.estimator.Estimator(model_fn=model_fn, config=config) +classifier.train(input_fn=input_fn) +``` + +That's it! This change will now configure estimator to run on all GPUs on your +machine, with the `MirroredStrategy` approach. It will take care of distributing +the input dataset, replicating layers and variables on each device, and +combining and applying gradients. + +The model and input functions do not have to change because we have changed the +underlying components of TensorFlow (such as +optimizer, batch norm and summaries) to become distribution-aware. +That means those components learn how to +combine their state across devices. Further, saving and checkpointing works +seamlessly, so you can save with one or no distribution strategy and resume with +another. + +Above, we showed the easiest way to use [`MirroredStrategy`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/distribute/MirroredStrategy#__init__). +There are few things you can customize in practice: + +* You can specify a list of specific GPUs (using param `devices`) or the number +of GPUs (using param `num_gpus`), in case you don't want auto detection. +* You can specify various parameters for all reduce with the `cross_tower_ops` +param, such as the all reduce algorithm to use, and gradient repacking. + +## Performance Tips + +We've tried to make it such that you get the best performance for your existing +model. We also recommend you follow the tips from +[Input Pipeline Performance Guide](https://www.tensorflow.org/performance/datasets_performance). +Specifically, we found using [`map_and_batch`](https://www.tensorflow.org/performance/datasets_performance#map_and_batch) +and [`dataset.prefetch`](https://www.tensorflow.org/performance/datasets_performance#pipelining) +in the input function gives a solid boost in performance. When using +`dataset.prefetch`, use `buffer_size=None` to let it detect optimal buffer size. + +## Caveats +This feature is in early stages and there are a lot of improvements forthcoming: + +* Metrics are not yet supported during distributed training. +* Summaries are currently computed in every tower. +* Evaluation is not yet distributed. +* Eager support is in the works; performance can be more challenging with eager +execution. +* As mentioned earlier, multi-node and other distributed strategies will be +introduced in the future. +* If you are [`batching`](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#batch) +your input data, we will place one batch on each GPU in each step. So your +effective batch size will be `num_gpus * batch_size`. Therefore, consider +adjusting your learning rate or batch size according to the number of GPUs. +We are working on addressing this limitation by splitting each batch across GPUs +instead. +* Dictionaries inside dataset in the input are not supported when prefetching +on GPUs is turned on. (If you need to use dictionaries in the dataset, turn off +prefetching on GPUs by passing param `prefetch_on_device=False` to +`MirroredStrategy`) + +## What's next? + +Please give distribution strategies a try. This feature is in early stages and +is evolving, so we welcome your feedback via +[issues on GitHub](https://github.com/tensorflow/tensorflow/issues/new). + + -- GitLab From 497dab37519a1856a52e6564d8eb1d03382911c3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 15:32:42 -0700 Subject: [PATCH 446/906] capture_tpu_profile will fallback to old behavior if user specify local directories as model directory. PiperOrigin-RevId: 190996878 --- tensorflow/contrib/tpu/profiler/capture_tpu_profile.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/tpu/profiler/capture_tpu_profile.cc b/tensorflow/contrib/tpu/profiler/capture_tpu_profile.cc index e6811d4ad2..f2003e04dd 100644 --- a/tensorflow/contrib/tpu/profiler/capture_tpu_profile.cc +++ b/tensorflow/contrib/tpu/profiler/capture_tpu_profile.cc @@ -70,8 +70,12 @@ ProfileResponse Profile(const string& service_addr, int duration_ms, ProfileRequest request; request.set_duration_ms(duration_ms); request.set_max_events(kMaxEvents); - request.set_repository_root(repository_root); - request.set_session_id(session_id); + if (tensorflow::str_util::StartsWith(repository_root, "gs://")) { + // For backward compatibilities, only generate tracetable etc when the + // user provide a GCS path for model directory. + request.set_repository_root(repository_root); + request.set_session_id(session_id); + } request.add_tools("input_pipeline"); request.add_tools("overview_page"); *request.mutable_opts() = opts; -- GitLab From af670bdc0e61802778f61778dd1623c87f30e874 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Thu, 29 Mar 2018 15:36:14 -0700 Subject: [PATCH 447/906] Undisables broken list_ops_test PiperOrigin-RevId: 190997355 --- tensorflow/core/kernels/list_kernels.h | 16 +++++++--------- tensorflow/python/kernel_tests/BUILD | 4 ---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tensorflow/core/kernels/list_kernels.h b/tensorflow/core/kernels/list_kernels.h index 8af48f0a67..f3bbf3b6e3 100644 --- a/tensorflow/core/kernels/list_kernels.h +++ b/tensorflow/core/kernels/list_kernels.h @@ -160,15 +160,13 @@ class TensorListFromTensor : public OpKernel { tmp_shape.RemoveDim(0); OP_REQUIRES(c, tmp.CopyFrom(tmp, tmp_shape), errors::Unknown("Unexpected shape error.")); - if (tmp.IsAligned() || !DataTypeCanUseMemcpy(DataTypeToEnum::value)) { - output_list.tensors.push_back(tmp); - } else { - Tensor aligned; - OP_REQUIRES_OK(c, c->allocate_temp(tmp.dtype(), tmp.shape(), &aligned)); - aligned.flat().device(c->eigen_device()) = - tmp.unaligned_flat(); - output_list.tensors.push_back(aligned); - } + // TODO(apassos) maybe not always align; but weird compiler bugs seem to + // prevent this. + Tensor aligned; + OP_REQUIRES_OK(c, c->allocate_temp(tmp.dtype(), tmp.shape(), &aligned)); + aligned.flat().device(c->eigen_device()) = + tmp.unaligned_flat(); + output_list.tensors.push_back(aligned); } output_tensor->scalar()() = std::move(output_list); } diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 5eceb9f768..ea210346c1 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -96,10 +96,6 @@ cuda_py_test( "//tensorflow/python:client_testlib", ], grpc_enabled = True, - tags = [ - "no_gpu", - "nogpu", - ], ) cuda_py_test( -- GitLab From 2bc52cd2d481a89c9724d20e827097efa4ff3f1e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 15:40:14 -0700 Subject: [PATCH 448/906] - Expose slim arg_scope function to compute keys to enable tessting. - Add is_training=None option to mobinenet arg_scopes. This allows the users to set is_training from an outer scope. PiperOrigin-RevId: 190997959 --- .../contrib/framework/python/ops/arg_scope.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tensorflow/contrib/framework/python/ops/arg_scope.py b/tensorflow/contrib/framework/python/ops/arg_scope.py index 3cad1fee19..5b15033995 100644 --- a/tensorflow/contrib/framework/python/ops/arg_scope.py +++ b/tensorflow/contrib/framework/python/ops/arg_scope.py @@ -68,7 +68,7 @@ from tensorflow.python.util import tf_decorator __all__ = [ 'arg_scope', 'add_arg_scope', 'current_arg_scope', 'has_arg_scope', - 'arg_scoped_arguments' + 'arg_scoped_arguments', 'arg_scope_func_key' ] _ARGSTACK = [{}] @@ -89,7 +89,7 @@ def current_arg_scope(): return stack[-1] -def _key_op(op): +def arg_scope_func_key(op): return getattr(op, '_key_op', str(op)) @@ -103,9 +103,9 @@ def _kwarg_names(func): def _add_op(op): - key_op = _key_op(op) - if key_op not in _DECORATED_OPS: - _DECORATED_OPS[key_op] = _kwarg_names(op) + key = arg_scope_func_key(op) + if key not in _DECORATED_OPS: + _DECORATED_OPS[key] = _kwarg_names(op) @tf_contextlib.contextmanager @@ -147,16 +147,16 @@ def arg_scope(list_ops_or_scope, **kwargs): try: current_scope = current_arg_scope().copy() for op in list_ops_or_scope: - key_op = _key_op(op) + key = arg_scope_func_key(op) if not has_arg_scope(op): raise ValueError('%s is not decorated with @add_arg_scope', _name_op(op)) - if key_op in current_scope: - current_kwargs = current_scope[key_op].copy() + if key in current_scope: + current_kwargs = current_scope[key].copy() current_kwargs.update(kwargs) - current_scope[key_op] = current_kwargs + current_scope[key] = current_kwargs else: - current_scope[key_op] = kwargs.copy() + current_scope[key] = kwargs.copy() _get_arg_stack().append(current_scope) yield current_scope finally: @@ -176,14 +176,14 @@ def add_arg_scope(func): def func_with_args(*args, **kwargs): current_scope = current_arg_scope() current_args = kwargs - key_func = _key_op(func) + key_func = arg_scope_func_key(func) if key_func in current_scope: current_args = current_scope[key_func].copy() current_args.update(kwargs) return func(*args, **current_args) _add_op(func) - setattr(func_with_args, '_key_op', _key_op(func)) + setattr(func_with_args, '_key_op', arg_scope_func_key(func)) return tf_decorator.make_decorator(func, func_with_args) @@ -196,7 +196,7 @@ def has_arg_scope(func): Returns: a boolean. """ - return _key_op(func) in _DECORATED_OPS + return arg_scope_func_key(func) in _DECORATED_OPS def arg_scoped_arguments(func): @@ -209,4 +209,4 @@ def arg_scoped_arguments(func): a list of kwargs names. """ assert has_arg_scope(func) - return _DECORATED_OPS[_key_op(func)] + return _DECORATED_OPS[arg_scope_func_key(func)] -- GitLab From e302cd64afacb5cc9057f03b5fbbee6315a33573 Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Thu, 29 Mar 2018 16:01:20 -0700 Subject: [PATCH 449/906] Updated eager guide to use tensorflow 1.7. Code snippets still work. PiperOrigin-RevId: 191001008 --- .../docs_src/programmers_guide/eager.md | 203 +----------------- 1 file changed, 5 insertions(+), 198 deletions(-) diff --git a/tensorflow/docs_src/programmers_guide/eager.md b/tensorflow/docs_src/programmers_guide/eager.md index 9ae1e602f4..8db65737dc 100644 --- a/tensorflow/docs_src/programmers_guide/eager.md +++ b/tensorflow/docs_src/programmers_guide/eager.md @@ -29,10 +29,10 @@ problem and share your benchmarks. ## Setup and basic usage -Install TensorFlow 1.7 to include the updates for eager execution: +Upgrade to TensorFlow 1.7 to include updates for eager execution: ``` -$ pip install --pre --upgrade tensorflow +$ pip install --upgrade tensorflow ``` To start eager execution, add `tf.enable_eager_execution()` to the beginning of @@ -322,14 +322,13 @@ grad_log1pexp(0.) # => [0.5] grad_log1pexp(100.) # => [nan] ``` - Here, the `log1pexp` function can be analytically simplified with a custom gradient. The implementation below reuses the value for `tf.exp(x)` that is computed during the forward pass—making it more efficient by eliminating redundant calculations: ```py -@tfe.custom_gradient +@tf.custom_gradient def log1pexp(x): e = tf.exp(x) def grad(dy): @@ -605,7 +604,7 @@ print(x) # => 2.0 ``` To save and load models, `tfe.Checkpoint` stores the internal state of objects, -without requiring hiiden variables. To record the state of a `model`, +without requiring hidden variables. To record the state of a `model`, an `optimizer`, and a global step, pass them to a `tfe.Checkpoint`: ```py @@ -649,9 +648,8 @@ inserted during model construction. For example, to record summaries once every 100 global steps: ```py -tf.train.get_or_create_global_step() # return global step var writer = tf.contrib.summary.create_file_writer(logdir) -global_step=tf.train.get_or_create_global_step() +global_step=tf.train.get_or_create_global_step() # return global step var writer.set_as_default() @@ -733,197 +731,6 @@ But this gap grows larger for models with less computation and there is work to be done for optimizing hot code paths for models with lots of small operations. -## Work with graphs - -While eager execution makes development and debugging more interactive, -TensorFlow graph execution has advantages for distributed training, performance -optimizations, and production deployment. However, writing graph code can feel -different than writing regular Python code and more difficult to debug. - -For building and training graph-constructed models, the Python program first -builds a graph representing the computation, then invokes `Session.run` to send -the graph for execution on the C++-based runtime. This provides: - -* Automatic differentiation using static autodiff. -* Simple deployment to a platform independent server. -* Graph-based optimizations (common subexpression elimination, constant-folding, etc.). -* Compilation and kernel fusion. -* Automatic distribution and replication (placing nodes on the distributed system). - -Deploying code written for eager execution is more difficult: either generate a -graph from the model, or run the Python runtime and code directly on the server. - -### Write compatible code - -The same code written for eager execution will also build a graph during graph -execution. Do this by simply running the same code in a new Python session where -eager execution is not enabled. - -Most TensorFlow operations work during eager execution, but there are some things -to keep in mind: - -* Use `tf.data` for input processing instead of queues. It's faster and easier. -* Use object-oriented layer APIs—like `tf.keras.layers` and - `tf.keras.Model`—since they have explicit storage for variables. -* Most model code works the same during eager and graph execution, but there are - exceptions. (For example, dynamic models using Python control flow to change the - computation based on inputs.) -* Once eager execution is enabled with `tf.enable_eager_execution`, it - cannot be turned off. Start a new Python session to return to graph execution. - -It's best to write code for both eager execution *and* graph execution. This -gives you eager's interactive experimentation and debuggability with the -distributed performance benefits of graph execution. - -Write, debug, and iterate in eager execution, then import the model graph for -production deployment. Use `tfe.Checkpoint` to save and restore model -variables, this allows movement between eager and graph execution environments. -See the examples in: -[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples). - -### Use eager execution in a graph environment - -Selectively enable eager execution in a TensorFlow graph environment using -`tfe.py_func`. This is used when `tf.enable_eager_execution()` has *not* -been called. - -```py -def my_py_func(x): - x = tf.matmul(x, x) # You can use tf ops - print(x) # but it's eager! - return x - -with tf.Session() as sess: - x = tf.placeholder(dtype=tf.float32) - # Call eager function in graph! - pf = tfe.py_func(my_py_func, [x], tf.float32) - sess.run(pf, feed_dict={x: [[2.0]]}) # [[4.0]] -``` - - -A `tfe.Checkpoint` stores the complete internal state of the objects passed to it. Nothing else is implicitly included. To record the state of a `model`, an `optimizer`, and a global step pass each one to the checkpoint's constructor: - -```py -model = MyModel() -optimizer = tf.train.AdamOptimizer(learning_rate=0.001) -checkpoint_dir = ‘/path/to/model_dir’ -checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") -root = tfe.Checkpoint(optimizer=optimizer, - model=model, - optimizer_step=tf.train.get_or_create_global_step()) - -root.save(file_prefix=checkpoint_prefix) -# or -root.restore(tf.train.latest_checkpoint(checkpoint_dir)) -``` - -### Object-oriented metrics - -`tfe.metrics` are stored as objects. Update a metric by passing the new data to -the callable, and retrieve the result using the `tfe.metrics.result` method, -for example: - -```py -m = tfe.metrics.Mean("loss") -m(0) -m(5) -m.result() # => 2.5 -m([8, 9]) -m.result() # => 5.5 -``` - -#### Summaries and TensorBoard - -@{$summaries_and_tensorboard$TensorBoard} is a visualization tool for -understanding, debugging and optimizing the model training process. It uses -summary events that are written while executing the program. - -`tf.contrib.summary` is compatible with both eager and graph execution -environments. Summary operations, such as `tf.contrib.summary.scalar`, are -inserted during model construction. For example, to record summaries once every -100 global steps: - -```py -tf.train.get_or_create_global_step() # return global step var -writer = tf.contrib.summary.create_file_writer(logdir) - -for _ in range(iterations): - with writer.as_default(): - with tf.contrib.summary.record_summaries_every_n_global_steps(100): - # your model code goes here - tf.contrib.summary.scalar('loss', loss) - ... -``` - -## Performance - -Computation is not automatically offloaded to GPUs during eager execution. To -explicitly direct a computation to a GPU, enclose it in a -`tf.device('/gpu:0')` block: - -```py -import time - -def measure(x, steps): - # TensorFlow initializes a GPU the first time it's used, exclude from timing. - tf.matmul(x, x) - start = time.time() - for i in range(steps): - x = tf.matmul(x, x) - _ = x.numpy() # Make sure to execute op and not just enqueue it - end = time.time() - return end - start - -shape = (1000, 1000) -steps = 200 -print("Time to multiply a {} matrix by itself {} times:".format(shape, steps)) - -# Run on CPU: -with tf.device("/cpu:0"): - print("CPU: {} secs".format(measure(tf.random_normal(shape), steps))) - -# Run on GPU, if available: -if tfe.num_gpus() > 0: - with tf.device("/gpu:0"): - print("GPU: {} secs".format(measure(tf.random_normal(shape), steps))) -else: - print("GPU: not found") -``` - -Output (exact numbers depend on hardware): - -``` -Time to multiply a (1000, 1000) matrix by itself 200 times: -CPU: 4.614904403686523 secs -GPU: 0.5581181049346924 secs -``` - -A `tf.Tensor` object can be copied to a different device to execute its -operations: - -```py -x = tf.random_normal([10, 10]) - -x_gpu0 = x.gpu() -x_cpu = x.cpu() - -_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU -_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0 - -if tfe.num_gpus() > 1: - x_gpu1 = x.gpu(1) - _ = tf.matmul(x_gpu1, x_gpu1) # Runs on GPU:1 -``` - -### Benchmarks - -For compute-heavy models, such as -[ResNet50](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/resnet50) -training on a GPU, eager execution performance is comparable to graph execution. -But this gap grows larger for models with less computation and there is work to -be done for optimizing hot code paths for models with lots of small operations. - - ## Work with graphs While eager execution makes development and debugging more interactive, -- GitLab From 0f01f076f86882104c4c358b2679cce1ad85057c Mon Sep 17 00:00:00 2001 From: Mark Heffernan Date: Thu, 29 Mar 2018 16:02:26 -0700 Subject: [PATCH 450/906] Add support for running benchmarks in XLA unit tests. In the XLA internal test 'main', parse the --benchmarks flag if it exists and runs the specified benchmarks. Previously microbenchmarks defined in unit tests were never run. PiperOrigin-RevId: 191001183 --- .../xla/tests/xla_internal_test_main.cc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tensorflow/compiler/xla/tests/xla_internal_test_main.cc b/tensorflow/compiler/xla/tests/xla_internal_test_main.cc index 92b2b1ee77..0af40bc15a 100644 --- a/tensorflow/compiler/xla/tests/xla_internal_test_main.cc +++ b/tensorflow/compiler/xla/tests/xla_internal_test_main.cc @@ -12,9 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ + #include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h" +#include "tensorflow/core/lib/core/stringpiece.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/test.h" +#include "tensorflow/core/platform/test_benchmark.h" GTEST_API_ int main(int argc, char** argv) { std::vector flag_list; @@ -25,7 +28,37 @@ GTEST_API_ int main(int argc, char** argv) { return 2; } + // If the --benchmarks flag is passed in then only run the benchmarks, not the + // tests. + for (int i = 1; i < argc; i++) { + tensorflow::StringPiece arg(argv[i]); + if (arg == "--benchmarks" || arg.starts_with("--benchmarks=")) { + const char* pattern = nullptr; + if (arg.starts_with("--benchmarks=")) { + pattern = argv[i] + strlen("--benchmarks="); + } else { + // Handle flag of the form '--benchmarks foo' (no '='). + if (i + 1 >= argc || + tensorflow::StringPiece(argv[i + 1]).starts_with("--")) { + LOG(ERROR) << "--benchmarks flag requires an argument."; + return 2; + } + pattern = argv[i + 1]; + } + // Unfortunately Google's internal benchmark infrastructure has a + // different API than Tensorflow's. +#if defined(PLATFORM_GOOGLE) + base::SetFlag(&FLAGS_benchmarks, pattern); + RunSpecifiedBenchmarks(); +#else + tensorflow::testing::Benchmark::Run(pattern); +#endif + return 0; + } + } + testing::InitGoogleTest(&argc, argv); + if (argc > 1) { LOG(ERROR) << "Unknown argument " << argv[1] << "\n" << usage; return 2; -- GitLab From 72205dadc2a973b746b3fdb6708429fd882a5d23 Mon Sep 17 00:00:00 2001 From: Priya Gupta Date: Thu, 29 Mar 2018 16:37:05 -0700 Subject: [PATCH 451/906] Minor language change in readme. PiperOrigin-RevId: 191006151 --- tensorflow/contrib/distribute/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/distribute/README.md b/tensorflow/contrib/distribute/README.md index ba9a392c77..4af51bec1a 100644 --- a/tensorflow/contrib/distribute/README.md +++ b/tensorflow/contrib/distribute/README.md @@ -89,8 +89,8 @@ combining and applying gradients. The model and input functions do not have to change because we have changed the underlying components of TensorFlow (such as -optimizer, batch norm and summaries) to become distribution-aware. -That means those components learn how to +optimizer, batch norm and summaries) to become distribution-aware. +That means those components know how to combine their state across devices. Further, saving and checkpointing works seamlessly, so you can save with one or no distribution strategy and resume with another. @@ -133,6 +133,7 @@ instead. on GPUs is turned on. (If you need to use dictionaries in the dataset, turn off prefetching on GPUs by passing param `prefetch_on_device=False` to `MirroredStrategy`) +* PartitionedVariables are not supported yet. ## What's next? -- GitLab From 1ba89338bdb4afb85ae56e64b47acc93a3a28703 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Thu, 29 Mar 2018 16:50:34 -0700 Subject: [PATCH 452/906] Fixing a subtle bug where in some cases the post cancellation work wasn't being done correctly. This is the scenario in which FunctionBufferingResource::Cancel() got called while buffering was being done, but then the buffer filled up in which case FillBuffer() wasn't ever called and the Cancel() method would get stuck waiting on a notification from the condition variable leading to timeouts. This CL fixes this by making sure FillBuffer() got called one last time in this case. Tested by running contrib/data/python/kernel_tests:prefetching_ops_test 500 times and ran contrib/distribute/python:values_test 500 times with no timeouts. PiperOrigin-RevId: 191007895 --- tensorflow/contrib/data/kernels/prefetching_kernels.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tensorflow/contrib/data/kernels/prefetching_kernels.cc b/tensorflow/contrib/data/kernels/prefetching_kernels.cc index 2afb8dbbf4..207f2820bf 100644 --- a/tensorflow/contrib/data/kernels/prefetching_kernels.cc +++ b/tensorflow/contrib/data/kernels/prefetching_kernels.cc @@ -224,6 +224,13 @@ class FunctionBufferingResource : public ResourceBase { if (buffer_.size() < buffer_size_ && !end_of_sequence_) { restart_buffering = true; } else { + // When the buffer is full, we don't want to call + // FillBuffer() unless we're in cancellation phase in which + // case FillBuffer() will do the final cleanup post + // cancellation. + if (cancelled_) { + restart_buffering = true; + } is_buffering_ = false; } } -- GitLab From 6628001dcc10c429aec33da186ff281d26729ac3 Mon Sep 17 00:00:00 2001 From: Nupur Garg Date: Thu, 29 Mar 2018 16:56:42 -0700 Subject: [PATCH 453/906] Updating documentation. PiperOrigin-RevId: 191008662 --- tensorflow/contrib/lite/toco/README.md | 33 +- .../lite/toco/g3doc/cmdline_examples.md | 404 ++++++++---------- .../lite/toco/g3doc/cmdline_reference.md | 119 ++---- .../contrib/lite/toco/g3doc/python_api.md | 7 + .../lite/toco/g3doc/toco_landscape.svg | 1 + 5 files changed, 258 insertions(+), 306 deletions(-) create mode 100644 tensorflow/contrib/lite/toco/g3doc/toco_landscape.svg diff --git a/tensorflow/contrib/lite/toco/README.md b/tensorflow/contrib/lite/toco/README.md index 281b2ea5e4..522e260ad2 100644 --- a/tensorflow/contrib/lite/toco/README.md +++ b/tensorflow/contrib/lite/toco/README.md @@ -1,26 +1,27 @@ -# The TensorFlow Lite Optimizing Converter +# TOCO: TensorFlow Lite Optimizing Converter -The TensorFlow Lite Optimizing Converter's most typical use is converting from the TensorFlow GraphDef to the TensorFlow Lite -format, but it supports much more than that. +The TensorFlow Lite Optimizing Converter converts TensorFlow graphs into +TensorFlow Lite graphs. There are additional usages that are also detailed in +the usage documentation. ## Usage documentation Usage information is given in these documents: +* [Command-line glossary](g3doc/cmdline_reference.md) * [Command-line examples](g3doc/cmdline_examples.md) -* [Command-line reference](g3doc/cmdline_reference.md) -* [Python API](g3doc/python_api.md) - -## Design documentation - -Coming soon! +* [Python API examples](g3doc/python_api.md) ## Where the converter fits in the TensorFlow landscape -In the typical case, an application developer is using TensorFlow to design and -train models, then uses TensorFlow's freeze_graph.py to generate a frozen -inference graph, then uses the converter to convert that into a TensorFlow Lite flatbuffer file, -then ships that file to client devices where the TensorFlow Lite interpreter handles them -on-device. This is represented in the following diagram: - -![drawing](https://storage.googleapis.com/download.tensorflow.org/example_images/tensorflow_landscape.svg) +Once an application developer has a trained TensorFlow model, TOCO will accept +that model and generate a TensorFlow Lite +[FlatBuffer](https://google.github.io/flatbuffers/) file. TOCO currently supports +[SavedModels](https://www.tensorflow.org/programmers_guide/saved_model#using_savedmodel_with_estimators) +and frozen graphs (models generated via +[freeze_graph.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/freeze_graph.py)). +The TensorFlow Lite FlatBuffer file can be shipped to client devices, generally +mobile devices, where the TensorFlow Lite interpreter handles them on-device. +This flow is represented in the diagram below. + +![drawing](g3doc/toco_landscape.svg) diff --git a/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md b/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md index 372c525589..495014c6fc 100644 --- a/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md +++ b/tensorflow/contrib/lite/toco/g3doc/cmdline_examples.md @@ -1,73 +1,72 @@ # TensorFlow Lite Optimizing Converter command-line examples -This page is a guide to using the TensorFlow Lite Optimizing Converter by -looking at some example command lines. It is complemented by the following other -documents: +This page provides examples on how to use TOCO via command line. It is +complemented by the following documents: * [README](../README.md) -* [Command-line reference](cmdline_reference.md) +* [Command-line glossary](cmdline_reference.md) +* [Python API examples](python_api.md) Table of contents: -[TOC] - -## Convert a TensorFlow GraphDef to TensorFlow Lite for float inference - -In this example, we look at the most common task: we have an ordinary TensorFlow -GraphDef and want to convert it to a TensorFlow Lite flatbuffer to perform -floating-point inference. +* [Convert a TensorFlow SavedModel to TensorFlow Lite](#savedmodel) +* [Convert a TensorFlow GraphDef to TensorFlow Lite for float + inference](#graphdef-float) +* [Quantization](#quantization) + * [Convert a TensorFlow GraphDef to TensorFlow Lite for quantized + inference](#graphdef-quant) + * [Use "dummy-quantization" to try out quantized inference on a float + graph](#dummy-quant) +* [Specifying input and output arrays](#specifying-input-and-output-arrays) + * [Multiple output arrays](#multiple-output-arrays) + * [Multiple input arrays](#multiple-input-arrays) + * [Specifying subgraphs](#specifying-subgraphs) +* [Other conversions supported by TOCO](#other-conversions) + * [Optimize a TensorFlow GraphDef](#optimize-graphdef) + * [Convert a TensorFlow Lite FlatBuffer back into TensorFlow GraphDef + format](#to-graphdef) +* [Logging](#logging) + * [Standard logging](#standard-logging) + * [Verbose logging](#verbose-logging) + * [Graph "video" logging](#graph-video-logging) +* [Graph visualizations](#graph-visualizations) + * [Using --output_format=GRAPHVIZ_DOT](#using-output-formatgraphviz-dot) + * [Using --dump_graphviz](#using-dump-graphviz) + * [Legend for the graph visualizations](#graphviz-legend) + +## Convert a TensorFlow SavedModel to TensorFlow Lite
+ +The follow example converts a basic TensorFlow SavedModel into a Tensorflow Lite +FlatBuffer to perform floating-point inference. ``` -curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ - | tar xzv -C /tmp bazel run --config=opt \ - //tensorflow/contrib/lite/toco:toco -- \ - --input_file=/tmp/mobilenet_v1_0.50_128/frozen_graph.pb \ - --output_file=/tmp/foo.tflite \ - --input_format=TENSORFLOW_GRAPHDEF \ - --output_format=TFLITE \ - --inference_type=FLOAT \ - --input_shape=1,128,128,3 \ - --input_array=input \ - --output_array=MobilenetV1/Predictions/Reshape_1 + third_party/tensorflow/contrib/lite/toco:toco -- \ + --savedmodel_directory=/tmp/saved_model \ + --output_file=/tmp/foo.tflite ``` -To explain each of these flags: - -* `--input_format` and `--output_format` determine the formats of the input - and output files: here we are converting from `TENSORFLOW_GRAPHDEF` to - `TFLITE`. -* `--input_file` specifies the path of the input file, to be converted. When - `--input_format=TENSORFLOW_GRAPHDEF`, this file should be a - *[frozen](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/freeze_graph.py)* - *inference* graph. Being frozen means in particular that the input file is - self-contained, and does not reference any external "checkpoint" file. An - *inference* graph is a version of a graph meant to be used for inference, - typically not the same graph file as was used for training a given model. -* `--output_file` specifies the destination to write the converted file to. -* `--input_array` specifies the input activations, that is, the input "tensor" - in the input TensorFlow GraphDef file. The array designated by - `--input_array` is the one that the user will have to provide the contents - of as input to the runtime inference code. -* `--output_array` specifies the output activations, that is, the output - "tensor" in the input TensorFlow GraphDef file. The runtime inference code - will store its results in the array designated by `--output_array`. -* `--input_shape` specifies the shape of the input array. It is currently - required, but the plan is for a future version to no longer require it, - allowing to defer the specification of the input shape until runtime. The - format of `input_shape` is always a comma-separated list of dimensions, - always in TensorFlow convention. -* `--inference_type` specifies what type of arithmetic the output file should - be relying on. It implies in particular the choice of type of the output - arrays in the output file. - -## Just optimize a TensorFlow GraphDef +[SavedModel](https://www.tensorflow.org/programmers_guide/saved_model#using_savedmodel_with_estimators) +has fewer required flags than frozen graphs (described [below](#graphdef-float)) +due to access to additional data contained within the SavedModel. The values for +`--input_arrays` and `--output_arrays` are an aggregated, alphabetized list of +the inputs and outputs in the +[SignatureDefs](https://www.tensorflow.org/serving/signature_defs) within the +[MetaGraphDef](https://www.tensorflow.org/programmers_guide/saved_model#apis_to_build_and_load_a_savedmodel) +specified by `--savedmodel_tagset`. The value for `input_shapes` is +automatically determined from the MetaGraphDef whenever possible. The default +value for `--inference_type` for SavedModels is `FLOAT`. -The converter accepts both TENSORFLOW_GRAPHDEF and TFLITE file formats as both -`--input_format` and `--output_format`. This means that conversion from and to -any supported format is possible, and in particular, same-format "conversions" -are possible, and effectively ask the converter to optimize and simplify a -graph. Example: +There is currently no support for MetaGraphDefs without a SignatureDef or for +MetaGraphDefs that use the [`assets/` +directory](https://www.tensorflow.org/programmers_guide/saved_model#structure_of_a_savedmodel_directory). + +## Convert a TensorFlow GraphDef to TensorFlow Lite for float inference + +The follow example converts a basic TensorFlow GraphDef (frozen by +[freeze_graph.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/freeze_graph.py)) +into a TensorFlow Lite FlatBuffer to perform floating-point inference. Frozen +graphs contain the variables stored in Checkpoint files as Const ops. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ @@ -75,56 +74,27 @@ curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_ bazel run --config=opt \ //tensorflow/contrib/lite/toco:toco -- \ --input_file=/tmp/mobilenet_v1_0.50_128/frozen_graph.pb \ - --output_file=/tmp/foo.pb \ - --input_format=TENSORFLOW_GRAPHDEF \ - --output_format=TENSORFLOW_GRAPHDEF \ + --output_file=/tmp/foo.tflite \ + --inference_type=FLOAT \ --input_shape=1,128,128,3 \ --input_array=input \ --output_array=MobilenetV1/Predictions/Reshape_1 ``` -Here we did not pass `--inference_type` because it is not considered applicable -to the TensorFlow GraphDef format (as far as we are concerned, TensorFlow -GraphDefs are technically always float, and the only flavor of "quantized" -GraphDef that the converter deals with is "FakeQuantized" graphs that are still -technically float graphs). +## Quantization -Below in the section about passing arbitrary input/output arrays we give another -example, using the converter to extract just a sub-graph from a TensorFlow -GraphDef. +### Convert a TensorFlow GraphDef to TensorFlow Lite for quantized inference -## Convert a TensorFlow Lite flatbuffer back into TensorFlow GraphDef format +TOCO is compatible with fixed point quantization models described +[here](https://www.tensorflow.org/performance/quantization). These are float +models with +[`FakeQuant*`](https://www.tensorflow.org/api_guides/python/array_ops#Fake_quantization) +ops inserted at the boundaries of fused layers to record min-max range +information. This generates a quantized inference workload that reproduces the +quantization behavior that was used during training. -As we mentioned that the converter supports file format conversions in any -direction, let us just give an example of that: - -``` -bazel run --config=opt \ - //tensorflow/contrib/lite/toco:toco -- \ - --input_file=/tmp/foo.tflite \ - --output_file=/tmp/foo.pb \ - --input_format=TFLITE \ - --output_format=TENSORFLOW_GRAPHDEF \ - --input_shape=1,128,128,3 \ - --input_array=input \ - --output_array=MobilenetV1/Predictions/Reshape_1 -``` - -## Convert a TensorFlow GraphDef to TensorFlow Lite for quantized inference - -Let us now look at a quantized model. As mentioned above, the only flavor of -quantized TensorFlow GraphDefs that the converter is concerned with, is -"FakeQuantized" models. These are technically float models, but with special -`FakeQuant*` ops inserted at the boundaries of fused layers to record min-max -range information allowing to generate a quantized inference workload that is -able to reproduce exactly the specific quantization behavior that was used -during training. Indeed, the whole point of quantized training is to allow for -both training and inference to perform exactly the same arithmetic, so that the -way that the training process about around quantization inaccuracy is -effectively helping the quantized inference process to be more accurate. - -Given a quantized TensorFlow GraphDef, generating a quantized TensorFlow Lite -flatbuffer is done like this: +The following command generates a quantized TensorFlow Lite FlatBuffer from a +"quantized" TensorFlow GraphDef. ``` bazel run --config=opt \ @@ -141,36 +111,17 @@ bazel run --config=opt \ --std_value=127 ``` -Here, besides changing `--input_file` to point to a (fake-)quantized GraphDef, -the only other changes are: - -* To change `--inference_type` to `QUANTIZED_UINT8`. This effectively tells - the converter to generate an output file that performs quantized inference - on a quantized input. -* To pass `--mean_value` and `--std_value` flags to describe how the quantized - uint8 input array values are to be interpreted as the mathematical real - numbers that the graph is concerned with (keep in mind that even a - "fake-quantized" TensorFlow GraphDef is still technically a float graph). - The meaning of `--mean_value` and `--std_value` is explained in the - command-line reference; it suffices for now to say that they are a property - of each model. +### Use \"dummy-quantization\" to try out quantized inference on a float graph -## Use dummy-quantization to try out quantized inference on a float graph +In order to evaluate the possible benefit of generating a quantized graph, TOCO +allows "dummy-quantization" on float graphs. The flags `--default_ranges_min` +and `--default_ranges_max` accept plausable values for the min-max ranges of the +values in all arrays that do not have min-max information. "Dummy-quantization" +will produce lower accuracy but will emulate the performance of a correctly +quantized model. -Sometimes, one only has a plain float graph, and one is curious as to how much -faster inference might run if one could perform quantized inference instead of -float inference. Rather than requiring users to first invest in quantizing their -graphs before they can evaluate a possible benefit, the converter allows to -simply experiment with what we call "dummy quantization": provide some vaguely -plausible values for the min-max ranges of values in all arrays that do not have -min-max information, so that quantization can carry on, certainly producing -inaccurate results (do not use that in production!) but with performance -characteristics that should be identical to those of an actually quantized -flavor of the model. - -In the present example, we have a model using Relu6 activation functions almost -everywhere, so a reasonable guess is that most activation ranges should be -contained in [0, 6] and roughly comparable to it. +The example below contains a model using Relu6 activation functions. Therefore, +a reasonable guess is that most activation ranges should be contained in [0, 6]. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ @@ -191,15 +142,13 @@ bazel run --config=opt \ --std_value=127.5 ``` -## Multiple output arrays +## Specifying input and output arrays -Some models have multiple outputs. Even in a model with only one output, you may -want for the inference code to return the contents of other arrays as well, or -to perform inference on a subgraph with multiple outputs (see the section below -on specifying arbitrary arrays as input/output arrays). +### Multiple output arrays -Either way, using `--output_arrays` instead of `--output_array` allows to -specify a comma-separated list of output arrays. +The flag `output_arrays` takes in a comma-separated list of output arrays as +seen in the example below. This is useful for models or subgraphs with multiple +outputs. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/inception_v1_2016_08_28_frozen.pb.tar.gz \ @@ -216,18 +165,11 @@ bazel run --config=opt \ --output_arrays=InceptionV1/InceptionV1/Mixed_3b/Branch_1/Conv2d_0a_1x1/Relu,InceptionV1/InceptionV1/Mixed_3b/Branch_2/Conv2d_0a_1x1/Relu ``` -## Multiple input arrays - -Some models have multiple inputs; even in a model with a single input, you may -want for the inference code to implement only a subgraph with multiple inputs -(see the section below on specifying arbitrary arrays as input/output arrays). +### Multiple input arrays -Either way, multiple input arrays are specified by using `--input_arrays` -instead of `--input_array` to specify a comma-separated list of input arrays. In -that case, one also needs to use `--input_shapes` instead of `--input_shape`. -The syntax for `--input_shapes` is a bit trickier, since already the singular -`--input_shape` was a comma-separated list of integers! Multiple input shapes -are delimited by a colon (`:`) in `--input_shapes`. +The flag `input_arrays` takes in a comma-separated list of input arrays as seen +in the example below. This is useful for models or subgraphs with multiple +inputs. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/inception_v1_2016_08_28_frozen.pb.tar.gz \ @@ -244,54 +186,93 @@ bazel run --config=opt \ --output_array=InceptionV1/Logits/Predictions/Reshape_1 ``` -## Specifying arbitrary arrays in a graph as input or output arrays +Note that `input_shapes` is provided as a colon-separated list. Each input shape +corresponds to the input array at the same position in the respective list. -Any array in the input file can be specified as an input or output array. This -allows to use the converter to extract a sub-graph out of the input graph file. -The converter then automatically discards any part of the graph that is not -needed for the subgraph identified by the specified input and output arrays. -Another use case for specifying multiple output arrays is to get inference code -to return the contents of some specified intermediate activations array, not -just the output activations. +### Specifying subgraphs -In order to know which array you want to pass as `--input_arrays` / -`--output_arrays`, it helps to have a visualization of the graph. See the -section below on graph visualization. When using graph visualization for that -purpose, make sure to use `--dump_graphviz=` to visualize exactly the graph as -it is in the actual final form being exported to the output file. +Any array in the input file can be specified as an input or output array in +order to extract subgraphs out of an input graph file. TOCO discards the parts +of the graph outside of the specific subgraph. Use [graph +visualizations](#graph-visualizations) to identify the input and output arrays +that make up the desired subgraph. + +The follow command shows how to extract a single fused layer out of a TensorFlow +GraphDef. + +``` +curl https://storage.googleapis.com/download.tensorflow.org/models/inception_v1_2016_08_28_frozen.pb.tar.gz \ + | tar xzv -C /tmp +bazel run --config=opt \ + //tensorflow/contrib/lite/toco:toco -- \ + --input_file=/tmp/inception_v1_2016_08_28_frozen.pb \ + --output_file=/tmp/foo.pb \ + --input_format=TENSORFLOW_GRAPHDEF \ + --output_format=TENSORFLOW_GRAPHDEF \ + --input_shapes=1,28,28,96:1,28,28,16:1,28,28,192:1,28,28,64 \ + --input_arrays=InceptionV1/InceptionV1/Mixed_3b/Branch_1/Conv2d_0a_1x1/Relu,InceptionV1/InceptionV1/Mixed_3b/Branch_2/Conv2d_0a_1x1/Relu,InceptionV1/InceptionV1/Mixed_3b/Branch_3/MaxPool_0a_3x3/MaxPool,InceptionV1/InceptionV1/Mixed_3b/Branch_0/Conv2d_0a_1x1/Relu \ + --output_array=InceptionV1/InceptionV1/Mixed_3b/concat_v2 +``` Note that the final representation of an on-device inference workload (say, in -TensorFlow Lite flatbuffers format) tends to have coarser granularity than the +TensorFlow Lite FlatBuffers format) tends to have coarser granularity than the very fine granularity of the TensorFlow GraphDef representation. For example, while a fully-connected layer is typically represented as at least four separate ops in TensorFlow GraphDef (Reshape, MatMul, BiasAdd, Relu...), it is typically represented as a single "fused" op (FullyConnected) in the converter's optimized representation and in the final on-device representation (e.g. in TensorFlow -Lite flatbuffer format). As the level of granularity gets coarser, some +Lite FlatBuffer format). As the level of granularity gets coarser, some intermediate arrays (say, the array between the MatMul and the BiasAdd in the TensorFlow GraphDef) are dropped. When specifying intermediate arrays as -`--input_arrays` / `--output_arrays`, it is generally at least desirable (and -often required) to specify arrays that are meant to survive in the final form of -the graph, after fusing. These are typically the outputs of activation functions -(since everything in each layer until the activation function tends to get -fused). +`--input_arrays` / `--output_arrays`, it is desirable (and often required) to +specify arrays that are meant to survive in the final form of the graph, after +fusing. These are typically the outputs of activation functions (since +everything in each layer until the activation function tends to get fused). + +## Other conversions supported by TOCO + +The converter accepts both TENSORFLOW_GRAPHDEF and TFLITE file formats as both +`--input_format` and `--output_format`. This means that conversion to and from +any supported format is possible. -Here is an example of extracting just a sub-graph, namely just a single fused -layer, out of a TensorFlow GraphDef, and exporting a TensorFlow GraphDef -containing just that subgraph: +### Optimize a TensorFlow GraphDef + +Same-format "conversions" can be used to optimize and simplify a graph or be +used to [get a subgraph](#specifying-subgraphs) of a graph. The flag +`--inference_type` is not required because TensorFlow graphs, including those +containing the +[`FakeQuant*`](https://www.tensorflow.org/api_guides/python/array_ops#Fake_quantization) +ops are always float graphs. ``` -curl https://storage.googleapis.com/download.tensorflow.org/models/inception_v1_2016_08_28_frozen.pb.tar.gz \ +curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ | tar xzv -C /tmp bazel run --config=opt \ //tensorflow/contrib/lite/toco:toco -- \ - --input_file=/tmp/inception_v1_2016_08_28_frozen.pb \ + --input_file=/tmp/mobilenet_v1_0.50_128/frozen_graph.pb \ --output_file=/tmp/foo.pb \ --input_format=TENSORFLOW_GRAPHDEF \ --output_format=TENSORFLOW_GRAPHDEF \ - --input_shapes=1,28,28,96:1,28,28,16:1,28,28,192:1,28,28,64 \ - --input_arrays=InceptionV1/InceptionV1/Mixed_3b/Branch_1/Conv2d_0a_1x1/Relu,InceptionV1/InceptionV1/Mixed_3b/Branch_2/Conv2d_0a_1x1/Relu,InceptionV1/InceptionV1/Mixed_3b/Branch_3/MaxPool_0a_3x3/MaxPool,InceptionV1/InceptionV1/Mixed_3b/Branch_0/Conv2d_0a_1x1/Relu \ - --output_array=InceptionV1/InceptionV1/Mixed_3b/concat_v2 + --input_shape=1,128,128,3 \ + --input_array=input \ + --output_array=MobilenetV1/Predictions/Reshape_1 +``` + +### Convert a TensorFlow Lite FlatBuffer back into TensorFlow GraphDef format + +The converter supports file format conversions from TensorFlow Lite, back into +TensorFlow GraphDef format. + +``` +bazel run --config=opt \ + //tensorflow/contrib/lite/toco:toco -- \ + --input_file=/tmp/foo.tflite \ + --output_file=/tmp/foo.pb \ + --input_format=TFLITE \ + --output_format=TENSORFLOW_GRAPHDEF \ + --input_shape=1,128,128,3 \ + --input_array=input \ + --output_array=MobilenetV1/Predictions/Reshape_1 ``` ## Logging @@ -299,8 +280,8 @@ bazel run --config=opt \ ### Standard logging The converter generates some informative log messages during processing. The -easiest way to view them is to add `--logtostderr` to command lines. For the -previous example, that gives: +easiest way to view them is to add `--logtostderr` to command lines as seen in +the following example. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ @@ -333,42 +314,34 @@ I1101 21:51:33.309484 5339 toco_tooling.cc:249] Estimated count of arithmetic For debugging purposes, the converter supports two levels of verbose logging, which can be set by passing a `--v=` flag: -* At `--v=1`, the converter generates text dumps of the graph at various - points during processing, as well as log messages about every graph - transformation that did take place, typically answering questions of the - form "why was my graph transformed in this way"? -* At `--v=2`, the converter additionally generates log messages about graph - transformations that were considered but not actually performed, typically - answering questions of the form "why was my graph NOT transformed when I - expected it would be?". +* For `--v=1`, the converter generates text dumps of the graph at various + points during processing as well as log messages about every graph + transformation that took place. +* For `--v=2`, the converter additionally generates log messages about graph + transformations that were considered but not performed. ### Graph "video" logging -When `--dump_graphviz=` is used (see the section on Graph visualizations), one -may additionally pass `--dump_graphviz_video`, which causes a graph -visualization to be dumped after each individual graph transformations, often -resulting in thousands of files. Typically, one would then bisect into these -files to understand when a given change was introduced in the graph. +When `--dump_graphviz=` is used (see the section on [graph +visualizations](#graph-visualizations)), one may additionally pass +`--dump_graphviz_video`, which causes a graph visualization to be dumped after +each individual graph transformation. This results in thousands of files. +Typically, one would then bisect into these files to understand when a given +change was introduced in the graph. ## Graph visualizations -The converter is able to export a graph to the GraphViz Dot format, for easy -visualization. Combined with the converter's ability to transform the graph into -a simpler, coarser-granularity representation, that makes it a very powerful -visualization tool. - -There are two ways to get the converter to export a GraphViz Dot file, -corresponding to two separate use cases. Understanding the difference between -them is key to getting useful graph visualizations. +TOCO can export a graph to the GraphViz Dot format for easy visualization via +either the `--output_format` flag or the `--dump_graphviz` flag. The subsections +below outline the use cases for each. ### Using `--output_format=GRAPHVIZ_DOT` -The first way to get a graphviz rendering is to pass -`--output_format=GRAPHVIZ_DOT`, instead of the `--output_format` that you would -otherwise use. This says: "I just want to get a plausible visualization of that -graph". The upside is that it makes for very simple command lines, and makes the -converter very lax about aspects of the graph or the command line that it would -otherwise complain about. Example: +The first way to get a graphviz rendering is to pass `GRAPHVIZ_DOT` into +`--output_format`. This results in a plausable visualization of the graph. This +reduces the requirements that normally exist during conversion between other +input and output formats. For example, this may be useful if conversion from +TENSORFLOW_GRAPHDEF to TFLITE is failing. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ @@ -391,7 +364,7 @@ dot -Tpdf -O /tmp/foo.dot ``` And the resulting `.dot.pdf` can be viewed in any PDF viewer, but we suggest one -with a good ability to pan and zoom across a very large page; Google Chrome does +with a good ability to pan and zoom across a very large page. Google Chrome does well in that respect. ``` @@ -400,14 +373,14 @@ google-chrome /tmp/foo.dot.pdf Example PDF files are viewable online in the next section. -### Using `--dump_graphviz=` +### Using `--dump_graphviz` -The second way to get a graphviz rendering is to pass a `--dump_graphviz=` flag -specifying a destination directory to dump GraphViz rendering to. Unlike the -previous approach, this one allows you to keep your real command-line (with your -real `--output_format` and other flags) unchanged, just appending a -`--dump_graphviz=` flag to it. This says: "I want visualizations of the actual -graph during this specific conversion process". Example: +The second way to get a graphviz rendering is to pass the `--dump_graphviz=` +flag, specifying a destination directory to dump GraphViz rendering to. Unlike +the previous approach, this one allows you to keep your real command-line (with +your real `--output_format` and other flags) unchanged, just appending a +`--dump_graphviz=` flag to it. This provides a visualization of the actual graph +during a specific conversion process. ``` curl https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_0.50_128_frozen.tgz \ @@ -425,8 +398,8 @@ bazel run --config=opt \ --dump_graphviz=/tmp ``` -This generates a few files in the destination directory, here `/tmp`. Most -important are these two files: +This generates a few files in the destination directory, here `/tmp`. The two +most important files are: ``` /tmp/toco_AT_IMPORT.dot @@ -442,8 +415,7 @@ conversion subsequently fails). `toco_AFTER_TRANSFORMATIONS.dot` represents the graph after all transformations were applied to it, just before it was exported to the `--output_file`. -Typically, this is a much smaller graph, and it conveys much more information -about each node. +Typically, this is a much smaller graph with more information about each node. Again, these can be rendered to PDFs: @@ -451,12 +423,12 @@ Again, these can be rendered to PDFs: dot -Tpdf -O /tmp/toco_*.dot ``` -The resulting files can be seen here: +Sample output files can be seen here: * [toco_AT_IMPORT.dot.pdf](https://storage.googleapis.com/download.tensorflow.org/example_images/toco_AT_IMPORT.dot.pdf) * [toco_AFTER_TRANSFORMATIONS.dot.pdf](https://storage.googleapis.com/download.tensorflow.org/example_images/toco_AFTER_TRANSFORMATIONS.dot.pdf). -### Legend for the graph visualizations +### Legend for the graph visualizations * Operators are red square boxes with the following hues of red: * Most operators are diff --git a/tensorflow/contrib/lite/toco/g3doc/cmdline_reference.md b/tensorflow/contrib/lite/toco/g3doc/cmdline_reference.md index 5e07795223..9e99287f82 100644 --- a/tensorflow/contrib/lite/toco/g3doc/cmdline_reference.md +++ b/tensorflow/contrib/lite/toco/g3doc/cmdline_reference.md @@ -1,84 +1,47 @@ -# TensorFlow Lite Optimizing Converter command-line reference +# TensorFlow Lite Optimizing Converter command-line glossary This page is complete reference of command-line flags. It is complemented by the following other documents: * [README](../README.md) * [Command-line examples](cmdline_examples.md) +* [Python API examples](python_api.md) Table of contents: -[TOC] - -## High-level overview - -A full list and detailed specification of all flags is given in the next -section. For now we focus on a higher-level description of command lines: - -``` -toco \ - --input_format=... \ - --output_format=... \ - --input_file=... \ - --output_file=... \ - [model flags...] \ - [transformation flags...] \ - [logging flags...] -``` - -In other words, the converter requires at least the following mandatory flags: -`--input_format`, `--output_format`, `--input_file`, `--output_file`. Depending -on the input and output formats, additional flags may be allowed or mandatory: - -* *Model flags* provide additional information about the model stored in the - input file. - * `--output_array` or `--output_arrays` specify which arrays in the input - file are to be considered the output activations. - * `--input_array` or `--input_arrays` specify which arrays in the input - file are to be considered the input activations. - * `--input_shape` or `--input_shapes` specify the shapes of the input - arrays. - * `--input_data_type` or `--input_data_types` specify the data types of - input arrays, which can be used if the input file does not already - specify them. - * `--mean_value` or `--mean_values`, and `--std_value` or `--std_values`, - give the dequantization parameters of the input arrays, for the case - when the output file will accept quantized input arrays. -* *Transformation flags* specify options of the transformations to be applied - to the graph, i.e. they specify requested properties that the output file - should have. - * `--inference_type` specifies the type of real-numbers arrays in the - output file. This only affects arrays of real numbers and allows to - control their quantization or dequantization, effectively switching - between floating-point and quantized arithmetic for the inference - workload, as far as real numbers are concerned. Other data types are - unaffected (e.g. plain integers, and strings). - * `--inference_input_type` is like `--inference_type` but specifically - controlling input arrays, separately from other arrays. If not - specified, then `--inference_type` is used. The use case for specifying - `--inference_input_type` is when one wants to perform floating-point - inference on a quantized input, as is common in image models operating - on bitmap image inputs. - * Some transformation flags allow to carry on with quantization when the - input graph is not properly quantized: `--default_ranges_min`, - `--default_ranges_max`, `--drop_fake_quant`, - `--reorder_across_fake_quant`. -* *Logging flags* described below. - -## Command-line flags complete reference - -### Mandatory flags - -* `--input_format`. Type: string. Specifies the format of the input file. - Allowed values: +* [High-level flags](#high-level-flags) +* [Model flags](#model-flags) +* [Transformation flags](#transformation-flags) +* [Logging flags](#logging-flags) + +## High-level flags + +The following high level flags specify the location of the input and output +files. The flag `--output_file` is always required. Additionally, either +`--input_file` or `--savedmodel_directory` is required. + +* `--savedmodel_directory`. Type: string. Specifies the full path to the + directory containing the SavedModel. +* `--savedmodel_tagset`. Type: string. Default: + [kSavedModelTagServe](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/cc/saved_model/tag_constants.h). + Specifies a comma-separated set of tags identifying the MetaGraphDef within + the SavedModel to analyze. All tags in the tag set must be specified. +* `--input_file`. Type: string. Specifies the path of the input file. This may + be either an absolute or a relative path. +* `--output_file`. Type: string. Specifies the path of the output file. + +The following high level flags specify the types of the input and output files: + +* `--input_format`. Type: string. Default: `TENSORFLOW_GRAPHDEF`. Specifies + the format of the input file. Allowed values: * `TENSORFLOW_GRAPHDEF` — The TensorFlow GraphDef format. Both binary and text proto formats are allowed. - * `TFLITE` — The TensorFlow Lite flatbuffers format. -* `--output_format`. Type: string. Specifies the format of the output file. - Allowed values: + * `TFLITE` — The TensorFlow Lite FlatBuffers format. +* `--output_format`. Type: string. Default: `TFLITE`. Specifies the format of + the output file. Allowed values: * `TENSORFLOW_GRAPHDEF` — The TensorFlow GraphDef format. Always produces a file in binary (not text) proto format. - * `TFLITE` — The TensorFlow Lite flatbuffers format. + * `TFLITE` — The TensorFlow Lite FlatBuffers format. * Whether a float or quantized TensorFlow Lite file will be produced depends on the `--inference_type` flag. * `GRAPHVIZ_DOT` — The GraphViz `.dot` format. This asks the @@ -95,11 +58,11 @@ on the input and output formats, additional flags may be allowed or mandatory: you get in your actual output format as opposed to just a merely plausible visualization of a model, consider using `--dump_graphviz` instead and keeping your true `--output_format`. -* `--input_file`. Type: string. Specifies the path of the input file. This may - be either an absolute or a relative path. -* `--output_file`. Type: string. Specifies the path of the output file. -### Model flags +## Model flags + +*Model flags* provide additional information about the model stored in the input +file. * `--output_array`. Type: string. Specifies a single array as the output activations. Incompatible with `--output_arrays`. @@ -111,6 +74,10 @@ on the input and output formats, additional flags may be allowed or mandatory: * `--input_arrays`. Type: comma-separated list of strings. Specifies a list of arrays as the input activations, for models with multiple inputs. Incompatible with `--input_array`. +* `--batch_size`. Type: integer. Default: 1. Specifies the batch size for the + model. Replaces the first dimension of an input size array if undefined. Use + only with SavedModels when neither `--input_shape` nor `input_shapes` flags + are specified. Incompatible with GraphDefs. When `--input_array` is used, the following flags are available to provide additional information about the single input array: @@ -160,7 +127,11 @@ additional information about the multiple input arrays: the input arrays specified in `--input_arrays`, in the same order. See `--mean_value`, `--std_value` for details. -### Transformation flags +## Transformation flags + +*Transformation flags* specify options of the transformations to be applied to +the graph, i.e. they specify requested properties that the output file should +have. * `--inference_type`. Type: string. Sets the type of real-number arrays in the output file, that is, controls the representation (quantization) of real @@ -232,7 +203,7 @@ additional information about the multiple input arrays: graph transformations on them, at the cost of no longer faithfully matching inference and training arithmetic. -### Logging flags +## Logging flags The following are standard Google logging flags: diff --git a/tensorflow/contrib/lite/toco/g3doc/python_api.md b/tensorflow/contrib/lite/toco/g3doc/python_api.md index 36e2d9c372..f0fd638a61 100644 --- a/tensorflow/contrib/lite/toco/g3doc/python_api.md +++ b/tensorflow/contrib/lite/toco/g3doc/python_api.md @@ -1,5 +1,12 @@ # TensorFlow Lite Optimizing Converter (TOCO) Python API reference +This page provides examples on how to use TOCO via the Python API. It is +complemented by the following documents: + +* [README](../README.md) +* [Command-line examples](cmdline_examples.md) +* [Command-line glossary](cmdline_reference.md) + ## High-level overview While the TensorFlow Lite Optimizing Converter can be used from the command diff --git a/tensorflow/contrib/lite/toco/g3doc/toco_landscape.svg b/tensorflow/contrib/lite/toco/g3doc/toco_landscape.svg new file mode 100644 index 0000000000..a47c088991 --- /dev/null +++ b/tensorflow/contrib/lite/toco/g3doc/toco_landscape.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From 6ccdb724858ac1ac343b47e73b75802e6e8fa004 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Thu, 29 Mar 2018 17:15:33 -0700 Subject: [PATCH 454/906] Add details of new mailing lists PiperOrigin-RevId: 191011187 --- tensorflow/docs_src/about/uses.md | 6 ++-- tensorflow/docs_src/community/contributing.md | 25 +++---------- tensorflow/docs_src/community/index.md | 6 +++- tensorflow/docs_src/community/leftnav_files | 1 + tensorflow/docs_src/community/lists.md | 32 ++++++++++++----- tensorflow/docs_src/community/swift.md | 35 +++++++++++++++++++ 6 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 tensorflow/docs_src/community/swift.md diff --git a/tensorflow/docs_src/about/uses.md b/tensorflow/docs_src/about/uses.md index d646880bd3..d3db98203e 100644 --- a/tensorflow/docs_src/about/uses.md +++ b/tensorflow/docs_src/about/uses.md @@ -18,9 +18,9 @@ This section describes some of the current uses of the TensorFlow system. > If you are using TensorFlow for research, for education, or for production > usage in some product, we would love to add something about your usage here. -> Please feel free to email us a brief description of how you're using -> TensorFlow, or even better, send us a pull request to add an entry to this -> file. +> Please feel free to [email us](mailto:usecases@tensorflow.org) a brief +> description of how you're using TensorFlow, or even better, send us a +> pull request to add an entry to this file. * **Deep Speech**
Version:CPU/GPU:Python Version:Compiler:Build Tools:cuDNN:CUDA:
tensorflow-1.7.0rc1CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.7.0rc1GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.6.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A
tensorflow_gpu-1.6.0GPU3.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow-1.5.0CPU3.5-3.6MSVC 2015 update 3Cmake v3.6.3N/AN/A